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
package/mcp_server.py DELETED
@@ -1,1800 +0,0 @@
1
- #!/usr/bin/env python3
2
- # SPDX-License-Identifier: MIT
3
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
4
- """SuperLocalMemory V2 - MCP Server
5
- Universal memory access for all MCP-compatible tools (Cursor, Windsurf, Claude Desktop, Continue.dev)
6
-
7
- IMPORTANT: This is an ADDITION to existing skills, not a replacement.
8
- Skills in Claude Code continue to work unchanged.
9
-
10
- Architecture:
11
- MCP Server (this file)
12
-
13
- Calls existing memory_store_v2.py
14
-
15
- Same SQLite database as skills
16
-
17
- Usage:
18
- # Run as stdio MCP server (for local IDEs)
19
- python3 mcp_server.py
20
-
21
- # Run as HTTP MCP server (for remote access)
22
- python3 mcp_server.py --transport http --port 8001
23
- """
24
- from mcp.server.fastmcp import FastMCP, Context
25
- from mcp.types import ToolAnnotations
26
- import sys
27
- import os
28
- import json
29
- import re
30
- import time
31
- import threading
32
- from pathlib import Path
33
- from typing import Optional, Dict, List, Any
34
-
35
- # Add src directory to path (use existing code!)
36
- MEMORY_DIR = Path.home() / ".claude-memory"
37
- sys.path.insert(0, str(MEMORY_DIR))
38
-
39
- # Import existing core modules (zero duplicate logic)
40
- try:
41
- from memory_store_v2 import MemoryStoreV2
42
- from graph_engine import GraphEngine
43
- from pattern_learner import PatternLearner
44
- except ImportError as e:
45
- print(f"Error: Could not import SuperLocalMemory modules: {e}", file=sys.stderr)
46
- print(f"Ensure SuperLocalMemory V2 is installed at {MEMORY_DIR}", file=sys.stderr)
47
- sys.exit(1)
48
-
49
- # Agent Registry + Provenance (v2.5+)
50
- try:
51
- from agent_registry import AgentRegistry
52
- from provenance_tracker import ProvenanceTracker
53
- PROVENANCE_AVAILABLE = True
54
- except ImportError:
55
- PROVENANCE_AVAILABLE = False
56
-
57
- # Trust Scorer (v2.6 — enforcement)
58
- try:
59
- from trust_scorer import TrustScorer
60
- TRUST_AVAILABLE = True
61
- except ImportError:
62
- TRUST_AVAILABLE = False
63
-
64
- # Qualixar Attribution (v2.8.3 — 3-layer provenance)
65
- try:
66
- from qualixar_attribution import QualixarSigner
67
- from qualixar_watermark import encode_watermark
68
- _signer = QualixarSigner("superlocalmemory", "2.8.3")
69
- ATTRIBUTION_AVAILABLE = True
70
- except ImportError:
71
- _signer = None
72
- ATTRIBUTION_AVAILABLE = False
73
-
74
-
75
- def _sign_response(response: dict) -> dict:
76
- """Apply Layer 2 cryptographic signing to MCP tool responses."""
77
- if _signer and isinstance(response, dict):
78
- try:
79
- return _signer.sign(response)
80
- except Exception:
81
- pass
82
- return response
83
-
84
- # Learning System (v2.7+)
85
- try:
86
- sys.path.insert(0, str(Path(__file__).parent / "src"))
87
- from learning import get_learning_db, get_adaptive_ranker, get_feedback_collector, get_engagement_tracker, get_status as get_learning_status
88
- from learning import FULL_LEARNING_AVAILABLE, ML_RANKING_AVAILABLE
89
- LEARNING_AVAILABLE = True
90
- except ImportError:
91
- LEARNING_AVAILABLE = False
92
-
93
- # ============================================================================
94
- # Synthetic Bootstrap Auto-Trigger (v2.7 — P1-12)
95
- # Runs ONCE on first recall if: memory count > 50, no model, LightGBM available.
96
- # Spawns in background thread — never blocks recall. All errors swallowed.
97
- # ============================================================================
98
-
99
- _bootstrap_checked = False
100
-
101
-
102
- def _maybe_bootstrap():
103
- """Check if synthetic bootstrap is needed and run it in a background thread.
104
-
105
- Called once from the first recall invocation. Sets _bootstrap_checked = True
106
- immediately to prevent re-entry. The actual bootstrap runs in a daemon thread
107
- so it never blocks the recall response.
108
-
109
- Conditions for bootstrap:
110
- 1. LEARNING_AVAILABLE and ML_RANKING_AVAILABLE flags are True
111
- 2. SyntheticBootstrapper.should_bootstrap() returns True (checks:
112
- - LightGBM + NumPy installed
113
- - No existing model file at ~/.claude-memory/models/ranker.txt
114
- - Memory count > 50)
115
-
116
- CRITICAL: This function wraps everything in try/except. Bootstrap failure
117
- must NEVER break recall. It is purely an optimization — first-time ML
118
- model creation so users don't have to wait 200+ recalls for personalization.
119
- """
120
- global _bootstrap_checked
121
- _bootstrap_checked = True # Set immediately to prevent re-entry
122
-
123
- try:
124
- if not LEARNING_AVAILABLE:
125
- return
126
- if not ML_RANKING_AVAILABLE:
127
- return
128
-
129
- from learning.synthetic_bootstrap import SyntheticBootstrapper
130
- bootstrapper = SyntheticBootstrapper(memory_db_path=DB_PATH)
131
-
132
- if not bootstrapper.should_bootstrap():
133
- return
134
-
135
- # Run bootstrap in background thread — never block recall
136
- import threading
137
-
138
- def _run_bootstrap():
139
- try:
140
- result = bootstrapper.bootstrap_model()
141
- if result:
142
- import logging
143
- logging.getLogger("superlocalmemory.mcp").info(
144
- "Synthetic bootstrap complete: %d samples",
145
- result.get('training_samples', 0)
146
- )
147
- except Exception:
148
- pass # Bootstrap failure is never critical
149
-
150
- thread = threading.Thread(target=_run_bootstrap, daemon=True)
151
- thread.start()
152
-
153
- except Exception:
154
- pass # Any failure in bootstrap setup is swallowed silently
155
-
156
-
157
- def _sanitize_error(error: Exception) -> str:
158
- """Strip internal paths and structure from error messages."""
159
- msg = str(error)
160
- # Strip file paths containing claude-memory
161
- msg = re.sub(r'/[\w./-]*claude-memory[\w./-]*', '[internal-path]', msg)
162
- # Strip file paths containing SuperLocalMemory
163
- msg = re.sub(r'/[\w./-]*SuperLocalMemory[\w./-]*', '[internal-path]', msg)
164
- # Strip SQLite table names from error messages
165
- msg = re.sub(r'table\s+\w+', 'table [redacted]', msg)
166
- return msg
167
-
168
-
169
- # Parse command line arguments early (needed for port in constructor)
170
- import argparse as _argparse
171
- _parser = _argparse.ArgumentParser(add_help=False)
172
- _parser.add_argument("--transport", default="stdio")
173
- _parser.add_argument("--port", type=int, default=8417)
174
- _pre_args, _ = _parser.parse_known_args()
175
-
176
- # Initialize MCP server
177
- mcp = FastMCP(
178
- name="SuperLocalMemory V2",
179
- host="127.0.0.1",
180
- port=_pre_args.port,
181
- )
182
-
183
- # Database path
184
- DB_PATH = MEMORY_DIR / "memory.db"
185
-
186
- # ============================================================================
187
- # Shared singleton instances (v2.5 — fixes per-call instantiation overhead)
188
- # All MCP tool handlers share one MemoryStoreV2 instance instead of creating
189
- # a new one per call. This means one ConnectionManager, one TF-IDF vectorizer,
190
- # one write queue — shared across all concurrent MCP requests.
191
- # ============================================================================
192
-
193
- _store = None
194
- _graph_engine = None
195
- _pattern_learner = None
196
-
197
-
198
- def get_store() -> MemoryStoreV2:
199
- """Get or create the shared MemoryStoreV2 singleton."""
200
- global _store
201
- if _store is None:
202
- _store = MemoryStoreV2(DB_PATH)
203
- return _store
204
-
205
-
206
- def get_graph_engine() -> GraphEngine:
207
- """Get or create the shared GraphEngine singleton."""
208
- global _graph_engine
209
- if _graph_engine is None:
210
- _graph_engine = GraphEngine(DB_PATH)
211
- return _graph_engine
212
-
213
-
214
- def get_pattern_learner() -> PatternLearner:
215
- """Get or create the shared PatternLearner singleton."""
216
- global _pattern_learner
217
- if _pattern_learner is None:
218
- _pattern_learner = PatternLearner(DB_PATH)
219
- return _pattern_learner
220
-
221
-
222
- _agent_registry = None
223
- _provenance_tracker = None
224
-
225
-
226
- def get_agent_registry() -> Optional[Any]:
227
- """Get shared AgentRegistry singleton (v2.5+). Returns None if unavailable."""
228
- global _agent_registry
229
- if not PROVENANCE_AVAILABLE:
230
- return None
231
- if _agent_registry is None:
232
- _agent_registry = AgentRegistry.get_instance(DB_PATH)
233
- return _agent_registry
234
-
235
-
236
- def get_provenance_tracker() -> Optional[Any]:
237
- """Get shared ProvenanceTracker singleton (v2.5+). Returns None if unavailable."""
238
- global _provenance_tracker
239
- if not PROVENANCE_AVAILABLE:
240
- return None
241
- if _provenance_tracker is None:
242
- _provenance_tracker = ProvenanceTracker.get_instance(DB_PATH)
243
- return _provenance_tracker
244
-
245
-
246
- _trust_scorer = None
247
-
248
-
249
- def get_trust_scorer() -> Optional[Any]:
250
- """Get shared TrustScorer singleton (v2.6+). Returns None if unavailable."""
251
- global _trust_scorer
252
- if not TRUST_AVAILABLE:
253
- return None
254
- if _trust_scorer is None:
255
- _trust_scorer = TrustScorer.get_instance(DB_PATH)
256
- return _trust_scorer
257
-
258
-
259
- def get_learning_components():
260
- """Get learning system components. Returns None if unavailable."""
261
- if not LEARNING_AVAILABLE:
262
- return None
263
- return {
264
- 'db': get_learning_db(),
265
- 'ranker': get_adaptive_ranker(),
266
- 'feedback': get_feedback_collector(),
267
- 'engagement': get_engagement_tracker(),
268
- }
269
-
270
-
271
- def _get_client_name(ctx: Optional[Context] = None) -> str:
272
- """Extract client name from MCP context, or return default.
273
-
274
- Reads clientInfo.name from the MCP initialize handshake via
275
- ctx.session.client_params. This identifies Perplexity, Codex,
276
- Claude Desktop, etc. as distinct agents.
277
- """
278
- if ctx:
279
- try:
280
- # Primary: session.client_params.clientInfo.name (from initialize handshake)
281
- session = getattr(ctx, 'session', None)
282
- if session:
283
- params = getattr(session, 'client_params', None)
284
- if params:
285
- client_info = getattr(params, 'clientInfo', None)
286
- if client_info:
287
- name = getattr(client_info, 'name', None)
288
- if name:
289
- return str(name)
290
- except Exception:
291
- pass
292
- try:
293
- # Fallback: ctx.client_id (per-request, may be null)
294
- client_id = ctx.client_id
295
- if client_id:
296
- return str(client_id)
297
- except Exception:
298
- pass
299
- return "mcp-client"
300
-
301
-
302
- def _register_mcp_agent(agent_name: str = "mcp-client", ctx: Optional[Context] = None):
303
- """Register the calling MCP agent and record activity. Non-blocking.
304
-
305
- v2.7.4: Extracts real client name from MCP context when available,
306
- so Perplexity, Codex, Claude Desktop show as distinct agents.
307
- """
308
- if ctx:
309
- detected = _get_client_name(ctx)
310
- if detected != "mcp-client":
311
- agent_name = detected
312
-
313
- registry = get_agent_registry()
314
- if registry:
315
- try:
316
- registry.register_agent(
317
- agent_id=f"mcp:{agent_name}",
318
- agent_name=agent_name,
319
- protocol="mcp",
320
- )
321
- except Exception:
322
- pass
323
-
324
-
325
- # ============================================================================
326
- # RECALL BUFFER & SIGNAL INFERENCE ENGINE (v2.7.4 — Silent Learning)
327
- # ============================================================================
328
- # Tracks recall operations and infers implicit feedback signals from user
329
- # behavior patterns. Zero user effort — all signals auto-collected.
330
- #
331
- # Signal Types:
332
- # implicit_positive_timegap — long pause (>5min) after recall = satisfied
333
- # implicit_negative_requick — quick re-query (<30s) = dissatisfied
334
- # implicit_positive_reaccess — same memory in consecutive recalls
335
- # implicit_positive_cross_tool — same memory recalled by different agents
336
- # implicit_positive_post_update — memory updated after being recalled
337
- # implicit_negative_post_delete — memory deleted after being recalled
338
- #
339
- # Research: Hu et al. 2008 (implicit feedback), BPR Rendle 2009 (pairwise)
340
- # ============================================================================
341
-
342
- class _RecallBuffer:
343
- """Thread-safe buffer tracking recent recall operations for signal inference.
344
-
345
- Stores the last recall per agent_id so we can compare consecutive recalls
346
- and infer whether the user found results useful.
347
-
348
- Rate limiting: max 5 implicit signals per agent per minute to prevent gaming.
349
- """
350
-
351
- def __init__(self):
352
- self._lock = threading.Lock()
353
- # {agent_id: {query, result_ids, timestamp, result_id_set}}
354
- self._last_recall: Dict[str, Dict[str, Any]] = {}
355
- # Global last recall (for cross-agent comparison)
356
- self._global_last: Optional[Dict[str, Any]] = None
357
- # Rate limiter: {agent_id: [timestamp, timestamp, ...]}
358
- self._signal_timestamps: Dict[str, List[float]] = {}
359
- # Set of memory_ids from the most recent recall (for post-action tracking)
360
- self._recent_result_ids: set = set()
361
- # Recall counter for passive decay auto-trigger
362
- self._recall_count: int = 0
363
- # Adaptive threshold: starts at 300s (5min), adjusts based on user patterns
364
- self._positive_threshold: float = 300.0
365
- self._inter_recall_times: List[float] = []
366
-
367
- def record_recall(
368
- self,
369
- query: str,
370
- result_ids: List[int],
371
- agent_id: str = "mcp-client",
372
- ) -> List[Dict[str, Any]]:
373
- """Record a recall and infer signals from previous recall comparison.
374
-
375
- Returns a list of inferred signal dicts: [{memory_id, signal_type, query}]
376
- """
377
- now = time.time()
378
- signals: List[Dict[str, Any]] = []
379
-
380
- with self._lock:
381
- self._recall_count += 1
382
- result_id_set = set(result_ids)
383
- self._recent_result_ids = result_id_set
384
-
385
- current = {
386
- "query": query,
387
- "result_ids": result_ids,
388
- "result_id_set": result_id_set,
389
- "timestamp": now,
390
- "agent_id": agent_id,
391
- }
392
-
393
- # --- Compare with previous recall from SAME agent ---
394
- prev = self._last_recall.get(agent_id)
395
- if prev:
396
- time_gap = now - prev["timestamp"]
397
-
398
- # Track inter-recall times for adaptive threshold
399
- self._inter_recall_times.append(time_gap)
400
- if len(self._inter_recall_times) > 100:
401
- self._inter_recall_times = self._inter_recall_times[-100:]
402
-
403
- # Update adaptive threshold (median of recent times, min 60s, max 1800s)
404
- if len(self._inter_recall_times) >= 10:
405
- sorted_times = sorted(self._inter_recall_times)
406
- median = sorted_times[len(sorted_times) // 2]
407
- self._positive_threshold = max(60.0, min(median * 0.8, 1800.0))
408
-
409
- # Signal: Quick re-query with different query = negative
410
- if time_gap < 30.0 and query != prev["query"]:
411
- for mid in prev["result_ids"][:5]: # Top 5 only
412
- signals.append({
413
- "memory_id": mid,
414
- "signal_type": "implicit_negative_requick",
415
- "query": prev["query"],
416
- "rank_position": prev["result_ids"].index(mid) + 1,
417
- })
418
-
419
- # Signal: Long pause = positive for previous results
420
- elif time_gap > self._positive_threshold:
421
- for mid in prev["result_ids"][:3]: # Top 3 only
422
- signals.append({
423
- "memory_id": mid,
424
- "signal_type": "implicit_positive_timegap",
425
- "query": prev["query"],
426
- "rank_position": prev["result_ids"].index(mid) + 1,
427
- })
428
-
429
- # Signal: Same memory re-accessed = positive
430
- overlap = result_id_set & prev["result_id_set"]
431
- for mid in overlap:
432
- signals.append({
433
- "memory_id": mid,
434
- "signal_type": "implicit_positive_reaccess",
435
- "query": query,
436
- })
437
-
438
- # --- Compare with previous recall from DIFFERENT agent (cross-tool) ---
439
- global_prev = self._global_last
440
- if global_prev and global_prev["agent_id"] != agent_id:
441
- cross_overlap = result_id_set & global_prev["result_id_set"]
442
- for mid in cross_overlap:
443
- signals.append({
444
- "memory_id": mid,
445
- "signal_type": "implicit_positive_cross_tool",
446
- "query": query,
447
- })
448
-
449
- # Update buffers
450
- self._last_recall[agent_id] = current
451
- self._global_last = current
452
-
453
- return signals
454
-
455
- def check_post_action(self, memory_id: int, action: str) -> Optional[Dict[str, Any]]:
456
- """Check if a memory action (update/delete) follows a recent recall.
457
-
458
- Returns signal dict if the memory was in recent results, else None.
459
- """
460
- with self._lock:
461
- if memory_id not in self._recent_result_ids:
462
- return None
463
-
464
- if action == "update":
465
- return {
466
- "memory_id": memory_id,
467
- "signal_type": "implicit_positive_post_update",
468
- "query": self._global_last["query"] if self._global_last else "",
469
- }
470
- elif action == "delete":
471
- return {
472
- "memory_id": memory_id,
473
- "signal_type": "implicit_negative_post_delete",
474
- "query": self._global_last["query"] if self._global_last else "",
475
- }
476
- return None
477
-
478
- def check_rate_limit(self, agent_id: str, max_per_minute: int = 5) -> bool:
479
- """Return True if agent is within rate limit, False if exceeded."""
480
- now = time.time()
481
- with self._lock:
482
- if agent_id not in self._signal_timestamps:
483
- self._signal_timestamps[agent_id] = []
484
-
485
- # Clean old timestamps (older than 60s)
486
- self._signal_timestamps[agent_id] = [
487
- ts for ts in self._signal_timestamps[agent_id]
488
- if now - ts < 60.0
489
- ]
490
-
491
- if len(self._signal_timestamps[agent_id]) >= max_per_minute:
492
- return False
493
-
494
- self._signal_timestamps[agent_id].append(now)
495
- return True
496
-
497
- def get_recall_count(self) -> int:
498
- """Get total recall count (for passive decay trigger)."""
499
- with self._lock:
500
- return self._recall_count
501
-
502
- def get_stats(self) -> Dict[str, Any]:
503
- """Get buffer statistics for diagnostics."""
504
- with self._lock:
505
- return {
506
- "recall_count": self._recall_count,
507
- "tracked_agents": len(self._last_recall),
508
- "positive_threshold_s": round(self._positive_threshold, 1),
509
- "recent_results_count": len(self._recent_result_ids),
510
- }
511
-
512
-
513
- # Module-level singleton
514
- _recall_buffer = _RecallBuffer()
515
-
516
-
517
- def _emit_implicit_signals(signals: List[Dict[str, Any]], agent_id: str = "mcp-client") -> int:
518
- """Emit inferred implicit signals to the feedback collector.
519
-
520
- Rate-limited: max 5 signals per agent per minute.
521
- All errors swallowed — signal collection must NEVER break operations.
522
-
523
- Returns number of signals actually stored.
524
- """
525
- if not LEARNING_AVAILABLE or not signals:
526
- return 0
527
-
528
- stored = 0
529
- try:
530
- feedback = get_feedback_collector()
531
- if not feedback:
532
- return 0
533
-
534
- for sig in signals:
535
- if not _recall_buffer.check_rate_limit(agent_id):
536
- break # Rate limit exceeded for this agent
537
- try:
538
- feedback.record_implicit_signal(
539
- memory_id=sig["memory_id"],
540
- query=sig.get("query", ""),
541
- signal_type=sig["signal_type"],
542
- source_tool=agent_id,
543
- rank_position=sig.get("rank_position"),
544
- )
545
- stored += 1
546
- except Exception:
547
- pass # Individual signal failure is fine
548
- except Exception:
549
- pass # Never break the caller
550
-
551
- return stored
552
-
553
-
554
- def _maybe_passive_decay() -> None:
555
- """Auto-trigger passive decay every 10 recalls in a background thread."""
556
- try:
557
- if not LEARNING_AVAILABLE:
558
- return
559
- if _recall_buffer.get_recall_count() % 10 != 0:
560
- return
561
-
562
- feedback = get_feedback_collector()
563
- if not feedback:
564
- return
565
-
566
- def _run_decay():
567
- try:
568
- count = feedback.compute_passive_decay(threshold=5)
569
- if count > 0:
570
- import logging
571
- logging.getLogger("superlocalmemory.mcp").info(
572
- "Passive decay: %d signals emitted", count
573
- )
574
- except Exception:
575
- pass
576
-
577
- thread = threading.Thread(target=_run_decay, daemon=True)
578
- thread.start()
579
- except Exception:
580
- pass
581
-
582
-
583
- # ============================================================================
584
- # Eager initialization — ensure schema migration runs at startup (v2.8)
585
- # ============================================================================
586
-
587
- def _eager_init():
588
- """Initialize all engines at startup. Ensures schema migration runs."""
589
- try:
590
- get_store() # Triggers MemoryStoreV2._init_db() which creates v2.8 columns
591
- except Exception:
592
- pass # Don't block server startup
593
- try:
594
- from lifecycle.lifecycle_engine import LifecycleEngine
595
- LifecycleEngine() # Triggers _ensure_columns()
596
- except Exception:
597
- pass
598
- try:
599
- from behavioral.outcome_tracker import OutcomeTracker
600
- OutcomeTracker(str(Path.home() / ".claude-memory" / "learning.db"))
601
- except Exception:
602
- pass
603
- try:
604
- from compliance.audit_db import AuditDB
605
- AuditDB(str(Path.home() / ".claude-memory" / "audit.db"))
606
- except Exception:
607
- pass
608
-
609
- # Run once at module load
610
- _eager_init()
611
-
612
-
613
- # ============================================================================
614
- # MCP TOOLS (Functions callable by AI)
615
- # ============================================================================
616
-
617
- @mcp.tool(annotations=ToolAnnotations(
618
- readOnlyHint=False,
619
- destructiveHint=False,
620
- openWorldHint=False,
621
- ))
622
- async def remember(
623
- content: str,
624
- tags: str = "",
625
- project: str = "",
626
- importance: int = 5,
627
- ctx: Context = None,
628
- ) -> dict:
629
- """
630
- Save content to SuperLocalMemory with intelligent indexing.
631
-
632
- This calls the SAME backend as /superlocalmemoryv2-remember skill.
633
- All memories are stored in the same local SQLite database.
634
-
635
- Args:
636
- content: The content to remember (required)
637
- tags: Comma-separated tags (optional, e.g. "python,api,backend")
638
- project: Project name to scope the memory
639
- importance: Importance score 1-10 (default 5)
640
-
641
- Returns:
642
- {
643
- "success": bool,
644
- "memory_id": int,
645
- "message": str,
646
- "content_preview": str
647
- }
648
-
649
- Examples:
650
- remember("Use FastAPI for REST APIs", tags="python,backend", project="myapp")
651
- remember("JWT auth with refresh tokens", tags="security,auth", importance=8)
652
- """
653
- try:
654
- # Register MCP agent (v2.5 — agent tracking, v2.7.4 — client detection)
655
- _register_mcp_agent(ctx=ctx)
656
-
657
- # Trust enforcement (v2.6) — block untrusted agents from writing
658
- try:
659
- trust = get_trust_scorer()
660
- if trust and not trust.check_trust("mcp:mcp-client", "write"):
661
- return {
662
- "success": False,
663
- "error": "Agent trust score too low for write operations",
664
- "message": "Trust enforcement blocked this operation"
665
- }
666
- except Exception:
667
- pass # Trust check failure should not block operations
668
-
669
- # Use existing MemoryStoreV2 class (no duplicate logic)
670
- store = get_store()
671
-
672
- # Call existing add_memory method
673
- memory_id = store.add_memory(
674
- content=content,
675
- tags=tags.split(",") if tags else None,
676
- project_name=project or None,
677
- importance=importance
678
- )
679
-
680
- # Record provenance (v2.5 — who created this memory)
681
- prov = get_provenance_tracker()
682
- if prov:
683
- try:
684
- prov.record_provenance(memory_id, created_by="mcp:client", source_protocol="mcp")
685
- except Exception:
686
- pass
687
-
688
- # Track write in agent registry
689
- registry = get_agent_registry()
690
- if registry:
691
- try:
692
- registry.record_write("mcp:mcp-client")
693
- except Exception:
694
- pass
695
-
696
- # Format response
697
- preview = content[:100] + "..." if len(content) > 100 else content
698
-
699
- return _sign_response({
700
- "success": True,
701
- "memory_id": memory_id,
702
- "message": f"Memory saved with ID {memory_id}",
703
- "content_preview": preview
704
- })
705
-
706
- except Exception as e:
707
- return {
708
- "success": False,
709
- "error": _sanitize_error(e),
710
- "message": "Failed to save memory"
711
- }
712
-
713
-
714
- @mcp.tool(annotations=ToolAnnotations(
715
- readOnlyHint=True,
716
- destructiveHint=False,
717
- openWorldHint=False,
718
- ))
719
- async def recall(
720
- query: str,
721
- limit: int = 10,
722
- min_score: float = 0.3,
723
- ctx: Context = None,
724
- ) -> dict:
725
- """
726
- Search memories using semantic similarity and knowledge graph.
727
- Results are personalized based on your usage patterns — the more you
728
- use SuperLocalMemory, the better results get. All learning is local.
729
-
730
- After using results, call memory_used(memory_id) for memories you
731
- referenced to help improve future recall quality.
732
-
733
- Args:
734
- query: Search query (required)
735
- limit: Maximum results to return (default 10)
736
- min_score: Minimum relevance score 0.0-1.0 (default 0.3)
737
-
738
- Returns:
739
- {
740
- "query": str,
741
- "results": [
742
- {
743
- "id": int,
744
- "content": str,
745
- "score": float,
746
- "tags": list,
747
- "project": str,
748
- "created_at": str
749
- }
750
- ],
751
- "count": int
752
- }
753
-
754
- Examples:
755
- recall("authentication patterns")
756
- recall("FastAPI", limit=5, min_score=0.5)
757
- """
758
- try:
759
- # Register MCP agent (v2.7.4 — client detection for agent tab)
760
- _register_mcp_agent(ctx=ctx)
761
-
762
- # Track recall in agent registry
763
- registry = get_agent_registry()
764
- if registry:
765
- try:
766
- agent_name = _get_client_name(ctx)
767
- registry.record_recall(f"mcp:{agent_name}")
768
- except Exception:
769
- pass
770
-
771
- # Use existing MemoryStoreV2 class
772
- store = get_store()
773
-
774
- # Hybrid search (opt-in via env var, v2.6)
775
- _use_hybrid = os.environ.get('SLM_HYBRID_SEARCH', 'false').lower() == 'true'
776
- if _use_hybrid:
777
- try:
778
- from hybrid_search import HybridSearchEngine
779
- engine = HybridSearchEngine(store=store)
780
- results = engine.search(query, limit=limit)
781
- except (ImportError, Exception):
782
- results = store.search(query, limit=limit)
783
- else:
784
- results = store.search(query, limit=limit)
785
-
786
- # v2.7: Auto-trigger synthetic bootstrap on first recall (P1-12)
787
- if not _bootstrap_checked:
788
- _maybe_bootstrap()
789
-
790
- # v2.7: Learning-based re-ranking (optional, graceful fallback)
791
- if LEARNING_AVAILABLE:
792
- try:
793
- ranker = get_adaptive_ranker()
794
- if ranker:
795
- results = ranker.rerank(results, query)
796
- except Exception:
797
- pass # Re-ranking failure must never break recall
798
-
799
- # Track recall for passive feedback decay
800
- if LEARNING_AVAILABLE:
801
- try:
802
- feedback = get_feedback_collector()
803
- if feedback:
804
- feedback.record_recall_results(query, [r.get('id') for r in results if r.get('id')])
805
- tracker = get_engagement_tracker()
806
- if tracker:
807
- tracker.record_activity('recall_performed', source='mcp')
808
- except Exception:
809
- pass # Tracking failure must never break recall
810
-
811
- # v2.7.4: Implicit signal inference from recall patterns
812
- try:
813
- result_ids = [r.get('id') for r in results if r.get('id')]
814
- signals = _recall_buffer.record_recall(query, result_ids)
815
- if signals:
816
- _emit_implicit_signals(signals)
817
- # Auto-trigger passive decay every 10 recalls
818
- _maybe_passive_decay()
819
- except Exception:
820
- pass # Signal inference must NEVER break recall
821
-
822
- # Filter by minimum score
823
- filtered_results = [
824
- r for r in results
825
- if r.get('score', 0) >= min_score
826
- ]
827
-
828
- return _sign_response({
829
- "success": True,
830
- "query": query,
831
- "results": filtered_results,
832
- "count": len(filtered_results),
833
- "total_searched": len(results)
834
- })
835
-
836
- except Exception as e:
837
- return {
838
- "success": False,
839
- "error": _sanitize_error(e),
840
- "message": "Failed to search memories",
841
- "results": [],
842
- "count": 0
843
- }
844
-
845
-
846
- @mcp.tool(annotations=ToolAnnotations(
847
- readOnlyHint=True,
848
- destructiveHint=False,
849
- openWorldHint=False,
850
- ))
851
- async def list_recent(limit: int = 10) -> dict:
852
- """
853
- List most recent memories.
854
-
855
- Args:
856
- limit: Number of memories to return (default 10)
857
-
858
- Returns:
859
- {
860
- "memories": list,
861
- "count": int
862
- }
863
- """
864
- try:
865
- # Use existing MemoryStoreV2 class
866
- store = get_store()
867
-
868
- # Call existing list_all method
869
- memories = store.list_all(limit=limit)
870
-
871
- return {
872
- "success": True,
873
- "memories": memories,
874
- "count": len(memories)
875
- }
876
-
877
- except Exception as e:
878
- return {
879
- "success": False,
880
- "error": _sanitize_error(e),
881
- "message": "Failed to list memories",
882
- "memories": [],
883
- "count": 0
884
- }
885
-
886
-
887
- @mcp.tool(annotations=ToolAnnotations(
888
- readOnlyHint=True,
889
- destructiveHint=False,
890
- openWorldHint=False,
891
- ))
892
- async def get_status() -> dict:
893
- """
894
- Get SuperLocalMemory system status and statistics.
895
-
896
- Returns:
897
- {
898
- "total_memories": int,
899
- "graph_clusters": int,
900
- "patterns_learned": int,
901
- "database_size_mb": float
902
- }
903
- """
904
- try:
905
- # Use existing MemoryStoreV2 class
906
- store = get_store()
907
-
908
- # Call existing get_stats method
909
- stats = store.get_stats()
910
-
911
- return _sign_response({
912
- "success": True,
913
- **stats
914
- })
915
-
916
- except Exception as e:
917
- return {
918
- "success": False,
919
- "error": _sanitize_error(e),
920
- "message": "Failed to get status"
921
- }
922
-
923
-
924
- @mcp.tool(annotations=ToolAnnotations(
925
- readOnlyHint=False,
926
- destructiveHint=False,
927
- openWorldHint=False,
928
- ))
929
- async def build_graph() -> dict:
930
- """
931
- Build or rebuild the knowledge graph from existing memories.
932
-
933
- This runs TF-IDF entity extraction and Leiden clustering to
934
- automatically discover relationships between memories.
935
-
936
- Returns:
937
- {
938
- "success": bool,
939
- "clusters_created": int,
940
- "memories_processed": int,
941
- "message": str
942
- }
943
- """
944
- try:
945
- # Use existing GraphEngine class
946
- engine = get_graph_engine()
947
-
948
- # Call existing build_graph method
949
- stats = engine.build_graph()
950
-
951
- return {
952
- "success": True,
953
- "message": "Knowledge graph built successfully",
954
- **stats
955
- }
956
-
957
- except Exception as e:
958
- return {
959
- "success": False,
960
- "error": _sanitize_error(e),
961
- "message": "Failed to build graph"
962
- }
963
-
964
-
965
- @mcp.tool(annotations=ToolAnnotations(
966
- readOnlyHint=False,
967
- destructiveHint=False,
968
- openWorldHint=False,
969
- ))
970
- async def switch_profile(name: str) -> dict:
971
- """
972
- Switch to a different memory profile.
973
-
974
- Profiles allow you to maintain separate memory contexts
975
- (e.g., work, personal, client projects). All profiles share
976
- one database — switching is instant and safe (no data copying).
977
-
978
- Args:
979
- name: Profile name to switch to
980
-
981
- Returns:
982
- {
983
- "success": bool,
984
- "profile": str,
985
- "message": str
986
- }
987
- """
988
- try:
989
- # Import profile manager (uses column-based profiles)
990
- sys.path.insert(0, str(MEMORY_DIR))
991
- from importlib import import_module
992
- # Use direct JSON config update for speed
993
- import json
994
- config_file = MEMORY_DIR / "profiles.json"
995
-
996
- if config_file.exists():
997
- with open(config_file, 'r') as f:
998
- config = json.load(f)
999
- else:
1000
- config = {'profiles': {'default': {'name': 'default', 'description': 'Default memory profile'}}, 'active_profile': 'default'}
1001
-
1002
- if name not in config.get('profiles', {}):
1003
- available = ', '.join(config.get('profiles', {}).keys())
1004
- return {
1005
- "success": False,
1006
- "message": f"Profile '{name}' not found. Available: {available}"
1007
- }
1008
-
1009
- old_profile = config.get('active_profile', 'default')
1010
- config['active_profile'] = name
1011
-
1012
- from datetime import datetime
1013
- config['profiles'][name]['last_used'] = datetime.now().isoformat()
1014
-
1015
- with open(config_file, 'w') as f:
1016
- json.dump(config, f, indent=2)
1017
-
1018
- return {
1019
- "success": True,
1020
- "profile": name,
1021
- "previous_profile": old_profile,
1022
- "message": f"Switched to profile '{name}'. Memory operations now use this profile."
1023
- }
1024
-
1025
- except Exception as e:
1026
- return {
1027
- "success": False,
1028
- "error": _sanitize_error(e),
1029
- "message": "Failed to switch profile"
1030
- }
1031
-
1032
-
1033
- @mcp.tool(annotations=ToolAnnotations(
1034
- readOnlyHint=True,
1035
- destructiveHint=False,
1036
- openWorldHint=False,
1037
- ))
1038
- async def backup_status() -> dict:
1039
- """
1040
- Get auto-backup system status for SuperLocalMemory.
1041
-
1042
- Returns backup configuration, last backup time, next scheduled backup,
1043
- total backup count, and storage used. Useful for monitoring data safety.
1044
-
1045
- Returns:
1046
- {
1047
- "enabled": bool,
1048
- "interval_display": str,
1049
- "last_backup": str or null,
1050
- "next_backup": str or null,
1051
- "backup_count": int,
1052
- "total_size_mb": float
1053
- }
1054
- """
1055
- try:
1056
- from auto_backup import AutoBackup
1057
- backup = AutoBackup()
1058
- status = backup.get_status()
1059
- return {
1060
- "success": True,
1061
- **status
1062
- }
1063
- except ImportError:
1064
- return {
1065
- "success": False,
1066
- "message": "Auto-backup module not installed. Update SuperLocalMemory to v2.4.0+.",
1067
- "enabled": False,
1068
- "backup_count": 0
1069
- }
1070
- except Exception as e:
1071
- return {
1072
- "success": False,
1073
- "error": _sanitize_error(e),
1074
- "message": "Failed to get backup status"
1075
- }
1076
-
1077
-
1078
- # ============================================================================
1079
- # LEARNING TOOLS (v2.7 — feedback, transparency, user control)
1080
- # ============================================================================
1081
-
1082
- @mcp.tool(annotations=ToolAnnotations(
1083
- readOnlyHint=False,
1084
- destructiveHint=False,
1085
- openWorldHint=True,
1086
- ))
1087
- async def memory_used(
1088
- memory_id: int,
1089
- query: str = "",
1090
- usefulness: str = "high"
1091
- ) -> dict:
1092
- """
1093
- Call this tool whenever you use information from a recalled memory in
1094
- your response. This is the most important feedback signal — it teaches
1095
- SuperLocalMemory which memories are truly useful and dramatically
1096
- improves future recall quality. All data stays 100% local.
1097
-
1098
- Best practice: After using recall() results, call memory_used() for
1099
- each memory ID you referenced. This takes <1ms and helps the system
1100
- learn your preferences.
1101
-
1102
- Args:
1103
- memory_id: ID of the useful memory (from recall results)
1104
- query: The recall query that found it (optional but recommended)
1105
- usefulness: How useful - "high", "medium", or "low" (default "high")
1106
-
1107
- Returns:
1108
- {"success": bool, "message": str}
1109
- """
1110
- try:
1111
- if not LEARNING_AVAILABLE:
1112
- return {"success": False, "message": "Learning features not available. Install: pip3 install lightgbm scipy"}
1113
-
1114
- feedback = get_feedback_collector()
1115
- if feedback is None:
1116
- return {"success": False, "message": "Feedback collector not initialized"}
1117
-
1118
- feedback.record_memory_used(
1119
- memory_id=memory_id,
1120
- query=query,
1121
- usefulness=usefulness,
1122
- source_tool="mcp-client",
1123
- )
1124
-
1125
- return {
1126
- "success": True,
1127
- "message": f"Feedback recorded for memory #{memory_id} (usefulness: {usefulness})"
1128
- }
1129
- except Exception as e:
1130
- return {"success": False, "error": _sanitize_error(e)}
1131
-
1132
-
1133
- @mcp.tool(annotations=ToolAnnotations(
1134
- readOnlyHint=True,
1135
- destructiveHint=False,
1136
- openWorldHint=False,
1137
- ))
1138
- async def get_learned_patterns(
1139
- min_confidence: float = 0.6,
1140
- category: str = "all"
1141
- ) -> dict:
1142
- """
1143
- See what SuperLocalMemory has learned about your preferences,
1144
- projects, and workflow patterns.
1145
-
1146
- Args:
1147
- min_confidence: Minimum confidence threshold 0.0-1.0 (default 0.6)
1148
- category: Filter by "tech", "workflow", "project", or "all" (default "all")
1149
-
1150
- Returns:
1151
- {
1152
- "success": bool,
1153
- "patterns": {
1154
- "tech_preferences": [...],
1155
- "workflow_patterns": [...],
1156
- },
1157
- "ranking_phase": str,
1158
- "feedback_count": int
1159
- }
1160
- """
1161
- try:
1162
- if not LEARNING_AVAILABLE:
1163
- return {"success": False, "message": "Learning features not available. Install: pip3 install lightgbm scipy", "patterns": {}}
1164
-
1165
- ldb = get_learning_db()
1166
- if ldb is None:
1167
- return {"success": False, "message": "Learning database not initialized", "patterns": {}}
1168
-
1169
- result = {"success": True, "patterns": {}}
1170
-
1171
- # Tech preferences (Layer 1)
1172
- if category in ("all", "tech"):
1173
- patterns = ldb.get_transferable_patterns(min_confidence=min_confidence)
1174
- result["patterns"]["tech_preferences"] = [
1175
- {
1176
- "id": p["id"],
1177
- "type": p["pattern_type"],
1178
- "key": p["key"],
1179
- "value": p["value"],
1180
- "confidence": round(p["confidence"], 2),
1181
- "evidence": p["evidence_count"],
1182
- "profiles_seen": p["profiles_seen"],
1183
- }
1184
- for p in patterns
1185
- ]
1186
-
1187
- # Workflow patterns (Layer 3)
1188
- if category in ("all", "workflow"):
1189
- workflows = ldb.get_workflow_patterns(min_confidence=min_confidence)
1190
- result["patterns"]["workflow_patterns"] = [
1191
- {
1192
- "id": p["id"],
1193
- "type": p["pattern_type"],
1194
- "key": p["pattern_key"],
1195
- "value": p["pattern_value"],
1196
- "confidence": round(p["confidence"], 2),
1197
- }
1198
- for p in workflows
1199
- ]
1200
-
1201
- # Ranking phase info
1202
- ranker = get_adaptive_ranker()
1203
- if ranker:
1204
- result["ranking_phase"] = ranker.get_phase()
1205
- result["feedback_count"] = ldb.get_feedback_count()
1206
-
1207
- # Learning stats
1208
- result["stats"] = ldb.get_stats()
1209
-
1210
- return result
1211
- except Exception as e:
1212
- return {"success": False, "error": _sanitize_error(e), "patterns": {}}
1213
-
1214
-
1215
- @mcp.tool(annotations=ToolAnnotations(
1216
- readOnlyHint=False,
1217
- destructiveHint=False,
1218
- openWorldHint=False,
1219
- ))
1220
- async def correct_pattern(
1221
- pattern_id: int,
1222
- correct_value: str,
1223
- reason: str = ""
1224
- ) -> dict:
1225
- """
1226
- Correct a learned pattern that is wrong. Use get_learned_patterns first
1227
- to see pattern IDs.
1228
-
1229
- Args:
1230
- pattern_id: ID of the pattern to correct
1231
- correct_value: The correct value (e.g., "Vue" instead of "React")
1232
- reason: Why the correction (optional)
1233
-
1234
- Returns:
1235
- {"success": bool, "message": str}
1236
- """
1237
- try:
1238
- if not LEARNING_AVAILABLE:
1239
- return {"success": False, "message": "Learning features not available"}
1240
-
1241
- ldb = get_learning_db()
1242
- if ldb is None:
1243
- return {"success": False, "message": "Learning database not initialized"}
1244
-
1245
- # Get existing pattern
1246
- conn = ldb._get_connection()
1247
- try:
1248
- cursor = conn.cursor()
1249
- cursor.execute('SELECT * FROM transferable_patterns WHERE id = ?', (pattern_id,))
1250
- pattern = cursor.fetchone()
1251
- if not pattern:
1252
- return {"success": False, "message": f"Pattern #{pattern_id} not found"}
1253
-
1254
- old_value = pattern['value']
1255
-
1256
- # Update the pattern with correction
1257
- ldb.upsert_transferable_pattern(
1258
- pattern_type=pattern['pattern_type'],
1259
- key=pattern['key'],
1260
- value=correct_value,
1261
- confidence=1.0, # User correction = maximum confidence
1262
- evidence_count=pattern['evidence_count'] + 1,
1263
- profiles_seen=pattern['profiles_seen'],
1264
- contradictions=[f"Corrected from '{old_value}' to '{correct_value}': {reason}"],
1265
- )
1266
-
1267
- # Record as negative feedback for the old value
1268
- feedback = get_feedback_collector()
1269
- if feedback:
1270
- feedback.record_memory_used(
1271
- memory_id=0, # No specific memory
1272
- query=f"correction:{pattern['key']}",
1273
- usefulness="low",
1274
- source_tool="mcp-correction",
1275
- )
1276
-
1277
- return {
1278
- "success": True,
1279
- "message": f"Pattern '{pattern['key']}' corrected: '{old_value}' → '{correct_value}'"
1280
- }
1281
- finally:
1282
- conn.close()
1283
- except Exception as e:
1284
- return {"success": False, "error": _sanitize_error(e)}
1285
-
1286
-
1287
- # ============================================================================
1288
- # CHATGPT CONNECTOR TOOLS (search + fetch — required by OpenAI MCP spec)
1289
- # These two tools are required for ChatGPT Connectors and Deep Research.
1290
- # They wrap existing SuperLocalMemory search/retrieval logic.
1291
- # Ref: https://platform.openai.com/docs/mcp
1292
- # ============================================================================
1293
-
1294
- @mcp.tool(annotations=ToolAnnotations(
1295
- readOnlyHint=True,
1296
- destructiveHint=False,
1297
- openWorldHint=False,
1298
- ))
1299
- async def search(query: str) -> dict:
1300
- """
1301
- Search for documents in SuperLocalMemory.
1302
-
1303
- Required by ChatGPT Connectors and Deep Research.
1304
- Returns a list of search results with id, title, text snippet, and url.
1305
-
1306
- Args:
1307
- query: Search query string. Natural language queries work best.
1308
-
1309
- Returns:
1310
- {"results": [{"id": str, "title": str, "text": str, "url": str}]}
1311
- """
1312
- try:
1313
- store = get_store()
1314
- raw_results = store.search(query, limit=20)
1315
-
1316
- # v2.7: Learning-based re-ranking (optional, graceful fallback)
1317
- if LEARNING_AVAILABLE:
1318
- try:
1319
- ranker = get_adaptive_ranker()
1320
- if ranker:
1321
- raw_results = ranker.rerank(raw_results, query)
1322
- except Exception:
1323
- pass # Re-ranking failure must never break search
1324
-
1325
- results = []
1326
- for r in raw_results:
1327
- if r.get('score', 0) < 0.2:
1328
- continue
1329
- content = r.get('content', '') or r.get('summary', '') or ''
1330
- snippet = content[:200] + "..." if len(content) > 200 else content
1331
- mem_id = str(r.get('id', ''))
1332
- title = r.get('category', 'Memory') + ': ' + (content[:60].replace('\n', ' ') if content else 'Untitled')
1333
- results.append({
1334
- "id": mem_id,
1335
- "title": title,
1336
- "text": snippet,
1337
- "url": f"memory://local/{mem_id}"
1338
- })
1339
-
1340
- return {"results": results}
1341
-
1342
- except Exception as e:
1343
- return {"results": [], "error": _sanitize_error(e)}
1344
-
1345
-
1346
- @mcp.tool(annotations=ToolAnnotations(
1347
- readOnlyHint=True,
1348
- destructiveHint=False,
1349
- openWorldHint=False,
1350
- ))
1351
- async def fetch(id: str) -> dict:
1352
- """
1353
- Retrieve full content of a memory by ID.
1354
-
1355
- Required by ChatGPT Connectors and Deep Research.
1356
- Use after search() to get complete document content for analysis and citation.
1357
-
1358
- Args:
1359
- id: Memory ID from search results.
1360
-
1361
- Returns:
1362
- {"id": str, "title": str, "text": str, "url": str, "metadata": dict|null}
1363
- """
1364
- try:
1365
- store = get_store()
1366
- mem = store.get_by_id(int(id))
1367
-
1368
- if not mem:
1369
- raise ValueError(f"Memory with ID {id} not found")
1370
-
1371
- content = mem.get('content', '') or mem.get('summary', '') or ''
1372
- title = (mem.get('category', 'Memory') or 'Memory') + ': ' + (content[:60].replace('\n', ' ') if content else 'Untitled')
1373
-
1374
- metadata = {}
1375
- if mem.get('tags'):
1376
- metadata['tags'] = mem['tags']
1377
- if mem.get('project_name'):
1378
- metadata['project'] = mem['project_name']
1379
- if mem.get('importance'):
1380
- metadata['importance'] = mem['importance']
1381
- if mem.get('cluster_id'):
1382
- metadata['cluster_id'] = mem['cluster_id']
1383
- if mem.get('created_at'):
1384
- metadata['created_at'] = mem['created_at']
1385
-
1386
- return {
1387
- "id": str(id),
1388
- "title": title,
1389
- "text": content,
1390
- "url": f"memory://local/{id}",
1391
- "metadata": metadata if metadata else None
1392
- }
1393
-
1394
- except Exception as e:
1395
- raise ValueError(f"Failed to fetch memory {id}: {_sanitize_error(e)}")
1396
-
1397
-
1398
- # ============================================================================
1399
- # v2.8 MCP TOOLS — Lifecycle, Behavioral Learning, Compliance
1400
- # ============================================================================
1401
-
1402
- try:
1403
- from mcp_tools_v28 import (
1404
- report_outcome as _report_outcome,
1405
- get_lifecycle_status as _get_lifecycle_status,
1406
- set_retention_policy as _set_retention_policy,
1407
- compact_memories as _compact_memories,
1408
- get_behavioral_patterns as _get_behavioral_patterns,
1409
- audit_trail as _audit_trail,
1410
- )
1411
-
1412
- V28_AVAILABLE = True
1413
-
1414
- @mcp.tool(annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False))
1415
- async def report_outcome(
1416
- memory_ids: list,
1417
- outcome: str,
1418
- action_type: str = "other",
1419
- context: str = None,
1420
- agent_id: str = "user",
1421
- project: str = None,
1422
- ) -> dict:
1423
- """Record action outcome for behavioral learning. Outcomes: success/failure/partial."""
1424
- return await _report_outcome(memory_ids, outcome, action_type, context, agent_id, project)
1425
-
1426
- @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False))
1427
- async def get_lifecycle_status(memory_id: int = None) -> dict:
1428
- """Get memory lifecycle status — state distribution or single memory state."""
1429
- return await _get_lifecycle_status(memory_id)
1430
-
1431
- @mcp.tool(annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False))
1432
- async def set_retention_policy(
1433
- name: str,
1434
- framework: str,
1435
- retention_days: int,
1436
- action: str = "retain",
1437
- applies_to_tags: list = None,
1438
- applies_to_project: str = None,
1439
- ) -> dict:
1440
- """Create a retention policy (GDPR, HIPAA, EU AI Act)."""
1441
- return await _set_retention_policy(
1442
- name, framework, retention_days, action, applies_to_tags, applies_to_project
1443
- )
1444
-
1445
- @mcp.tool(annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False))
1446
- async def compact_memories(dry_run: bool = True, profile: str = None) -> dict:
1447
- """Evaluate and compact stale memories through lifecycle transitions. dry_run=True by default.
1448
-
1449
- Args:
1450
- dry_run: If True (default), show what would happen without changes.
1451
- profile: Profile name to filter.
1452
- """
1453
- return await _compact_memories(dry_run, profile)
1454
-
1455
- @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False))
1456
- async def get_behavioral_patterns(
1457
- min_confidence: float = 0.0, project: str = None
1458
- ) -> dict:
1459
- """Get learned behavioral patterns from outcome analysis."""
1460
- return await _get_behavioral_patterns(min_confidence, project)
1461
-
1462
- @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False))
1463
- async def audit_trail(
1464
- event_type: str = None,
1465
- actor: str = None,
1466
- limit: int = 50,
1467
- verify_chain: bool = False,
1468
- ) -> dict:
1469
- """Query compliance audit trail with optional hash chain verification."""
1470
- return await _audit_trail(event_type, actor, limit, verify_chain)
1471
-
1472
- except ImportError:
1473
- V28_AVAILABLE = False # v2.8 tools unavailable — graceful degradation
1474
-
1475
-
1476
- # ============================================================================
1477
- # MCP RESOURCES (Data endpoints)
1478
- # ============================================================================
1479
-
1480
- @mcp.resource("memory://recent/{limit}")
1481
- async def get_recent_memories_resource(limit: str) -> str:
1482
- """
1483
- Resource: Get N most recent memories.
1484
-
1485
- Usage: memory://recent/10
1486
- """
1487
- try:
1488
- store = get_store()
1489
- memories = store.list_all(limit=int(limit))
1490
- return json.dumps(memories, indent=2)
1491
- except Exception as e:
1492
- return json.dumps({"error": _sanitize_error(e)}, indent=2)
1493
-
1494
-
1495
- @mcp.resource("memory://stats")
1496
- async def get_stats_resource() -> str:
1497
- """
1498
- Resource: Get system statistics.
1499
-
1500
- Usage: memory://stats
1501
- """
1502
- try:
1503
- store = get_store()
1504
- stats = store.get_stats()
1505
- return json.dumps(stats, indent=2)
1506
- except Exception as e:
1507
- return json.dumps({"error": _sanitize_error(e)}, indent=2)
1508
-
1509
-
1510
- @mcp.resource("memory://graph/clusters")
1511
- async def get_clusters_resource() -> str:
1512
- """
1513
- Resource: Get knowledge graph clusters.
1514
-
1515
- Usage: memory://graph/clusters
1516
- """
1517
- try:
1518
- engine = get_graph_engine()
1519
- stats = engine.get_stats()
1520
- clusters = stats.get('clusters', [])
1521
- return json.dumps(clusters, indent=2)
1522
- except Exception as e:
1523
- return json.dumps({"error": _sanitize_error(e)}, indent=2)
1524
-
1525
-
1526
- @mcp.resource("memory://patterns/identity")
1527
- async def get_coding_identity_resource() -> str:
1528
- """
1529
- Resource: Get learned coding identity and patterns.
1530
-
1531
- Usage: memory://patterns/identity
1532
- """
1533
- try:
1534
- learner = get_pattern_learner()
1535
- patterns = learner.get_identity_context(min_confidence=0.5)
1536
- return json.dumps(patterns, indent=2)
1537
- except Exception as e:
1538
- return json.dumps({"error": _sanitize_error(e)}, indent=2)
1539
-
1540
-
1541
- @mcp.resource("memory://learning/status")
1542
- async def get_learning_status_resource() -> str:
1543
- """
1544
- Resource: Get learning system status.
1545
-
1546
- Usage: memory://learning/status
1547
- """
1548
- try:
1549
- if not LEARNING_AVAILABLE:
1550
- return json.dumps({"available": False, "message": "Learning deps not installed"}, indent=2)
1551
- status = get_learning_status()
1552
- return json.dumps(status, indent=2)
1553
- except Exception as e:
1554
- return json.dumps({"error": _sanitize_error(e)}, indent=2)
1555
-
1556
-
1557
- @mcp.resource("memory://engagement")
1558
- async def get_engagement_resource() -> str:
1559
- """
1560
- Resource: Get engagement metrics.
1561
-
1562
- Usage: memory://engagement
1563
- """
1564
- try:
1565
- if not LEARNING_AVAILABLE:
1566
- return json.dumps({"available": False}, indent=2)
1567
- tracker = get_engagement_tracker()
1568
- if tracker:
1569
- stats = tracker.get_engagement_stats()
1570
- return json.dumps(stats, indent=2)
1571
- return json.dumps({"available": False}, indent=2)
1572
- except Exception as e:
1573
- return json.dumps({"error": _sanitize_error(e)}, indent=2)
1574
-
1575
-
1576
- # ============================================================================
1577
- # MCP PROMPTS (Template injection)
1578
- # ============================================================================
1579
-
1580
- @mcp.prompt()
1581
- async def coding_identity_prompt() -> str:
1582
- """
1583
- Generate prompt with user's learned coding identity.
1584
-
1585
- Inject this at the start of conversations for personalized assistance
1586
- based on learned preferences and patterns.
1587
- """
1588
- try:
1589
- learner = get_pattern_learner()
1590
- patterns = learner.get_identity_context(min_confidence=0.6)
1591
-
1592
- if not patterns:
1593
- return "# Coding Identity\n\nNo patterns learned yet. Use remember() to save coding decisions and preferences."
1594
-
1595
- prompt = "# Your Coding Identity (Learned from History)\n\n"
1596
- prompt += "SuperLocalMemory has learned these patterns from your past decisions:\n\n"
1597
-
1598
- if 'frameworks' in patterns:
1599
- prompt += f"**Preferred Frameworks:** {', '.join(patterns['frameworks'])}\n"
1600
-
1601
- if 'style' in patterns:
1602
- prompt += f"**Coding Style:** {', '.join(patterns['style'])}\n"
1603
-
1604
- if 'testing' in patterns:
1605
- prompt += f"**Testing Approach:** {', '.join(patterns['testing'])}\n"
1606
-
1607
- if 'api_style' in patterns:
1608
- prompt += f"**API Style:** {', '.join(patterns['api_style'])}\n"
1609
-
1610
- prompt += "\n*Use this context to provide personalized suggestions aligned with established preferences.*"
1611
-
1612
- return prompt
1613
-
1614
- except Exception as e:
1615
- return f"# Coding Identity\n\nError loading patterns: {_sanitize_error(e)}"
1616
-
1617
-
1618
- @mcp.prompt()
1619
- async def project_context_prompt(project_name: str) -> str:
1620
- """
1621
- Generate prompt with project-specific context.
1622
-
1623
- Args:
1624
- project_name: Name of the project to get context for
1625
-
1626
- Returns:
1627
- Formatted prompt with relevant project memories
1628
- """
1629
- try:
1630
- store = get_store()
1631
-
1632
- # Search for project-related memories
1633
- memories = store.search(f"project:{project_name}", limit=20)
1634
-
1635
- if not memories:
1636
- return f"# Project Context: {project_name}\n\nNo memories found for this project. Use remember() with project='{project_name}' to save project-specific context."
1637
-
1638
- prompt = f"# Project Context: {project_name}\n\n"
1639
- prompt += f"Found {len(memories)} relevant memories:\n\n"
1640
-
1641
- for i, mem in enumerate(memories[:10], 1):
1642
- prompt += f"{i}. {mem['content'][:150]}\n"
1643
- if mem.get('tags'):
1644
- prompt += f" Tags: {', '.join(mem['tags'])}\n"
1645
- prompt += "\n"
1646
-
1647
- if len(memories) > 10:
1648
- prompt += f"\n*Showing top 10 of {len(memories)} total memories.*"
1649
-
1650
- return prompt
1651
-
1652
- except Exception as e:
1653
- return f"# Project Context: {project_name}\n\nError loading context: {_sanitize_error(e)}"
1654
-
1655
-
1656
- # ============================================================================
1657
- # SERVER STARTUP
1658
- # ============================================================================
1659
-
1660
- @mcp.tool(annotations=ToolAnnotations(
1661
- readOnlyHint=True,
1662
- destructiveHint=False,
1663
- openWorldHint=False,
1664
- ))
1665
- async def get_attribution() -> dict:
1666
- """
1667
- Get creator attribution and provenance verification for SuperLocalMemory.
1668
-
1669
- Returns creator information, license details, and verification status
1670
- for the 3-layer Qualixar attribution system.
1671
-
1672
- Returns:
1673
- {
1674
- "creator": str,
1675
- "license": str,
1676
- "platform": str,
1677
- "layers": {
1678
- "visible": bool,
1679
- "cryptographic": bool,
1680
- "steganographic": bool
1681
- }
1682
- }
1683
- """
1684
- try:
1685
- store = get_store()
1686
- attribution = store.get_attribution()
1687
-
1688
- return _sign_response({
1689
- "success": True,
1690
- **attribution,
1691
- "website": "https://superlocalmemory.com",
1692
- "author_website": "https://varunpratap.com",
1693
- "attribution_layers": {
1694
- "layer1_visible": True,
1695
- "layer2_cryptographic": ATTRIBUTION_AVAILABLE,
1696
- "layer3_steganographic": ATTRIBUTION_AVAILABLE,
1697
- },
1698
- })
1699
-
1700
- except Exception as e:
1701
- return {
1702
- "success": False,
1703
- "error": _sanitize_error(e),
1704
- "message": "Failed to get attribution"
1705
- }
1706
-
1707
-
1708
- if __name__ == "__main__":
1709
- import argparse
1710
-
1711
- # Parse command line arguments
1712
- parser = argparse.ArgumentParser(
1713
- description="SuperLocalMemory V2 - MCP Server for Universal IDE Integration"
1714
- )
1715
- parser.add_argument(
1716
- "--transport",
1717
- choices=["stdio", "http", "sse", "streamable-http"],
1718
- default="stdio",
1719
- help="Transport method: stdio for local IDEs (default), sse/streamable-http for ChatGPT and remote access"
1720
- )
1721
- parser.add_argument(
1722
- "--port",
1723
- type=int,
1724
- default=8417,
1725
- help="Port for HTTP transport (default 8417)"
1726
- )
1727
-
1728
- args = parser.parse_args()
1729
-
1730
- # Print startup message to stderr (stdout is used for MCP protocol)
1731
- print("=" * 60, file=sys.stderr)
1732
- print("SuperLocalMemory V2 - MCP Server", file=sys.stderr)
1733
- print("Version: 2.8.3", file=sys.stderr)
1734
- print("=" * 60, file=sys.stderr)
1735
- print("Created by: Varun Pratap Bhardwaj (Solution Architect)", file=sys.stderr)
1736
- print("Repository: https://github.com/varun369/SuperLocalMemoryV2", file=sys.stderr)
1737
- print("License: MIT (attribution required - see ATTRIBUTION.md)", file=sys.stderr)
1738
- print("=" * 60, file=sys.stderr)
1739
- print("", file=sys.stderr)
1740
- print(f"Transport: {args.transport}", file=sys.stderr)
1741
-
1742
- if args.transport == "http":
1743
- print(f"Port: {args.port}", file=sys.stderr)
1744
-
1745
- print(f"Database: {DB_PATH}", file=sys.stderr)
1746
- print("", file=sys.stderr)
1747
- print("MCP Tools Available:", file=sys.stderr)
1748
- print(" - remember(content, tags, project, importance)", file=sys.stderr)
1749
- print(" - recall(query, limit, min_score)", file=sys.stderr)
1750
- print(" - search(query) [ChatGPT Connector]", file=sys.stderr)
1751
- print(" - fetch(id) [ChatGPT Connector]", file=sys.stderr)
1752
- print(" - list_recent(limit)", file=sys.stderr)
1753
- print(" - get_status()", file=sys.stderr)
1754
- print(" - build_graph()", file=sys.stderr)
1755
- print(" - switch_profile(name) [Project/Profile switch]", file=sys.stderr)
1756
- print(" - backup_status() [Auto-Backup]", file=sys.stderr)
1757
- if LEARNING_AVAILABLE:
1758
- print(" - memory_used(memory_id, query, usefulness) [v2.7 Learning]", file=sys.stderr)
1759
- print(" - get_learned_patterns(min_confidence, category) [v2.7 Learning]", file=sys.stderr)
1760
- print(" - correct_pattern(pattern_id, correct_value) [v2.7 Learning]", file=sys.stderr)
1761
- if V28_AVAILABLE:
1762
- print(" - report_outcome(memory_ids, outcome) [v2.8 Behavioral]", file=sys.stderr)
1763
- print(" - get_lifecycle_status(memory_id) [v2.8 Lifecycle]", file=sys.stderr)
1764
- print(" - set_retention_policy(name, framework, days) [v2.8 Compliance]", file=sys.stderr)
1765
- print(" - compact_memories(dry_run) [v2.8 Lifecycle]", file=sys.stderr)
1766
- print(" - get_behavioral_patterns(min_confidence) [v2.8 Behavioral]", file=sys.stderr)
1767
- print(" - audit_trail(event_type, verify_chain) [v2.8 Compliance]", file=sys.stderr)
1768
- print("", file=sys.stderr)
1769
- print("MCP Resources Available:", file=sys.stderr)
1770
- print(" - memory://recent/{limit}", file=sys.stderr)
1771
- print(" - memory://stats", file=sys.stderr)
1772
- print(" - memory://graph/clusters", file=sys.stderr)
1773
- print(" - memory://patterns/identity", file=sys.stderr)
1774
- if LEARNING_AVAILABLE:
1775
- print(" - memory://learning/status", file=sys.stderr)
1776
- print(" - memory://engagement", file=sys.stderr)
1777
- print("", file=sys.stderr)
1778
- print("MCP Prompts Available:", file=sys.stderr)
1779
- print(" - coding_identity_prompt()", file=sys.stderr)
1780
- print(" - project_context_prompt(project_name)", file=sys.stderr)
1781
- print("", file=sys.stderr)
1782
- print("Status: Starting server...", file=sys.stderr)
1783
- print("=" * 60, file=sys.stderr)
1784
- print("", file=sys.stderr)
1785
-
1786
- # Run MCP server
1787
- if args.transport == "stdio":
1788
- # stdio transport for local IDEs (default)
1789
- mcp.run(transport="stdio")
1790
- elif args.transport == "streamable-http":
1791
- # Streamable HTTP transport (recommended for ChatGPT 2026+)
1792
- print(f"Streamable HTTP server at http://localhost:{args.port}", file=sys.stderr)
1793
- print("ChatGPT setup: expose via ngrok, paste URL in Settings > Connectors", file=sys.stderr)
1794
- mcp.run(transport="streamable-http")
1795
- else:
1796
- # SSE transport for remote access (ChatGPT, web clients)
1797
- # "http" is accepted as alias for "sse"
1798
- print(f"HTTP/SSE server will be available at http://localhost:{args.port}", file=sys.stderr)
1799
- print("ChatGPT setup: expose via ngrok, paste URL in Settings > Connectors", file=sys.stderr)
1800
- mcp.run(transport="sse")