superlocalmemory 2.8.5 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +9 -1
  3. package/NOTICE +63 -0
  4. package/README.md +165 -480
  5. package/bin/slm +17 -449
  6. package/bin/slm-npm +2 -2
  7. package/bin/slm.bat +4 -2
  8. package/conftest.py +5 -0
  9. package/docs/api-reference.md +284 -0
  10. package/docs/architecture.md +149 -0
  11. package/docs/auto-memory.md +150 -0
  12. package/docs/cli-reference.md +276 -0
  13. package/docs/compliance.md +191 -0
  14. package/docs/configuration.md +182 -0
  15. package/docs/getting-started.md +102 -0
  16. package/docs/ide-setup.md +261 -0
  17. package/docs/mcp-tools.md +220 -0
  18. package/docs/migration-from-v2.md +170 -0
  19. package/docs/profiles.md +173 -0
  20. package/docs/troubleshooting.md +310 -0
  21. package/{configs → ide/configs}/antigravity-mcp.json +3 -3
  22. package/ide/configs/chatgpt-desktop-mcp.json +16 -0
  23. package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
  24. package/{configs → ide/configs}/codex-mcp.toml +4 -4
  25. package/{configs → ide/configs}/continue-mcp.yaml +4 -3
  26. package/{configs → ide/configs}/continue-skills.yaml +6 -6
  27. package/ide/configs/cursor-mcp.json +15 -0
  28. package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
  29. package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
  30. package/{configs → ide/configs}/opencode-mcp.json +2 -2
  31. package/{configs → ide/configs}/perplexity-mcp.json +2 -2
  32. package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
  33. package/{configs → ide/configs}/windsurf-mcp.json +3 -3
  34. package/{configs → ide/configs}/zed-mcp.json +2 -2
  35. package/{hooks → ide/hooks}/context-hook.js +9 -20
  36. package/ide/hooks/memory-list-skill.js +70 -0
  37. package/ide/hooks/memory-profile-skill.js +101 -0
  38. package/ide/hooks/memory-recall-skill.js +62 -0
  39. package/ide/hooks/memory-remember-skill.js +68 -0
  40. package/ide/hooks/memory-reset-skill.js +160 -0
  41. package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
  42. package/ide/integrations/langchain/README.md +106 -0
  43. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
  44. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
  45. package/ide/integrations/langchain/pyproject.toml +38 -0
  46. package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
  47. package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
  48. package/ide/integrations/langchain/tests/test_security.py +117 -0
  49. package/ide/integrations/llamaindex/README.md +81 -0
  50. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
  51. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
  52. package/ide/integrations/llamaindex/pyproject.toml +43 -0
  53. package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
  54. package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
  55. package/ide/integrations/llamaindex/tests/test_security.py +241 -0
  56. package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
  57. package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
  58. package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
  59. package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
  60. package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
  61. package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
  62. package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
  63. package/package.json +13 -22
  64. package/pyproject.toml +85 -0
  65. package/scripts/build-dmg.sh +417 -0
  66. package/scripts/install-skills.ps1 +334 -0
  67. package/{install.ps1 → scripts/install.ps1} +36 -4
  68. package/{install.sh → scripts/install.sh} +14 -13
  69. package/scripts/postinstall.js +2 -2
  70. package/scripts/start-dashboard.ps1 +52 -0
  71. package/scripts/start-dashboard.sh +41 -0
  72. package/scripts/sync-wiki.ps1 +127 -0
  73. package/scripts/sync-wiki.sh +82 -0
  74. package/scripts/test-dmg.sh +161 -0
  75. package/scripts/test-npm-package.ps1 +252 -0
  76. package/scripts/test-npm-package.sh +207 -0
  77. package/scripts/verify-install.ps1 +294 -0
  78. package/scripts/verify-install.sh +266 -0
  79. package/src/superlocalmemory/__init__.py +0 -0
  80. package/src/superlocalmemory/attribution/__init__.py +9 -0
  81. package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
  82. package/src/superlocalmemory/attribution/signer.py +153 -0
  83. package/src/superlocalmemory/attribution/watermark.py +189 -0
  84. package/src/superlocalmemory/cli/__init__.py +5 -0
  85. package/src/superlocalmemory/cli/commands.py +245 -0
  86. package/src/superlocalmemory/cli/main.py +89 -0
  87. package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
  88. package/src/superlocalmemory/cli/post_install.py +99 -0
  89. package/src/superlocalmemory/cli/setup_wizard.py +129 -0
  90. package/src/superlocalmemory/compliance/__init__.py +0 -0
  91. package/src/superlocalmemory/compliance/abac.py +204 -0
  92. package/src/superlocalmemory/compliance/audit.py +314 -0
  93. package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
  94. package/src/superlocalmemory/compliance/gdpr.py +294 -0
  95. package/src/superlocalmemory/compliance/lifecycle.py +158 -0
  96. package/src/superlocalmemory/compliance/retention.py +232 -0
  97. package/src/superlocalmemory/compliance/scheduler.py +148 -0
  98. package/src/superlocalmemory/core/__init__.py +0 -0
  99. package/src/superlocalmemory/core/config.py +391 -0
  100. package/src/superlocalmemory/core/embeddings.py +293 -0
  101. package/src/superlocalmemory/core/engine.py +701 -0
  102. package/src/superlocalmemory/core/hooks.py +65 -0
  103. package/src/superlocalmemory/core/maintenance.py +172 -0
  104. package/src/superlocalmemory/core/modes.py +140 -0
  105. package/src/superlocalmemory/core/profiles.py +234 -0
  106. package/src/superlocalmemory/core/registry.py +117 -0
  107. package/src/superlocalmemory/dynamics/__init__.py +0 -0
  108. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
  109. package/src/superlocalmemory/encoding/__init__.py +0 -0
  110. package/src/superlocalmemory/encoding/consolidator.py +485 -0
  111. package/src/superlocalmemory/encoding/emotional.py +125 -0
  112. package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
  113. package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
  114. package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
  115. package/src/superlocalmemory/encoding/foresight.py +91 -0
  116. package/src/superlocalmemory/encoding/graph_builder.py +302 -0
  117. package/src/superlocalmemory/encoding/observation_builder.py +160 -0
  118. package/src/superlocalmemory/encoding/scene_builder.py +183 -0
  119. package/src/superlocalmemory/encoding/signal_inference.py +90 -0
  120. package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
  121. package/src/superlocalmemory/encoding/type_router.py +235 -0
  122. package/src/superlocalmemory/hooks/__init__.py +3 -0
  123. package/src/superlocalmemory/hooks/auto_capture.py +111 -0
  124. package/src/superlocalmemory/hooks/auto_recall.py +93 -0
  125. package/src/superlocalmemory/hooks/ide_connector.py +204 -0
  126. package/src/superlocalmemory/hooks/rules_engine.py +99 -0
  127. package/src/superlocalmemory/infra/__init__.py +3 -0
  128. package/src/superlocalmemory/infra/auth_middleware.py +82 -0
  129. package/src/superlocalmemory/infra/backup.py +317 -0
  130. package/src/superlocalmemory/infra/cache_manager.py +267 -0
  131. package/src/superlocalmemory/infra/event_bus.py +381 -0
  132. package/src/superlocalmemory/infra/rate_limiter.py +135 -0
  133. package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
  134. package/src/superlocalmemory/learning/__init__.py +0 -0
  135. package/src/superlocalmemory/learning/adaptive.py +172 -0
  136. package/src/superlocalmemory/learning/behavioral.py +490 -0
  137. package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
  138. package/src/superlocalmemory/learning/bootstrap.py +298 -0
  139. package/src/superlocalmemory/learning/cross_project.py +399 -0
  140. package/src/superlocalmemory/learning/database.py +376 -0
  141. package/src/superlocalmemory/learning/engagement.py +323 -0
  142. package/src/superlocalmemory/learning/features.py +138 -0
  143. package/src/superlocalmemory/learning/feedback.py +316 -0
  144. package/src/superlocalmemory/learning/outcomes.py +255 -0
  145. package/src/superlocalmemory/learning/project_context.py +366 -0
  146. package/src/superlocalmemory/learning/ranker.py +155 -0
  147. package/src/superlocalmemory/learning/source_quality.py +303 -0
  148. package/src/superlocalmemory/learning/workflows.py +309 -0
  149. package/src/superlocalmemory/llm/__init__.py +0 -0
  150. package/src/superlocalmemory/llm/backbone.py +316 -0
  151. package/src/superlocalmemory/math/__init__.py +0 -0
  152. package/src/superlocalmemory/math/fisher.py +356 -0
  153. package/src/superlocalmemory/math/langevin.py +398 -0
  154. package/src/superlocalmemory/math/sheaf.py +257 -0
  155. package/src/superlocalmemory/mcp/__init__.py +0 -0
  156. package/src/superlocalmemory/mcp/resources.py +245 -0
  157. package/src/superlocalmemory/mcp/server.py +61 -0
  158. package/src/superlocalmemory/mcp/tools.py +18 -0
  159. package/src/superlocalmemory/mcp/tools_core.py +305 -0
  160. package/src/superlocalmemory/mcp/tools_v28.py +223 -0
  161. package/src/superlocalmemory/mcp/tools_v3.py +286 -0
  162. package/src/superlocalmemory/retrieval/__init__.py +0 -0
  163. package/src/superlocalmemory/retrieval/agentic.py +295 -0
  164. package/src/superlocalmemory/retrieval/ann_index.py +223 -0
  165. package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
  166. package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
  167. package/src/superlocalmemory/retrieval/engine.py +390 -0
  168. package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
  169. package/src/superlocalmemory/retrieval/fusion.py +78 -0
  170. package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
  171. package/src/superlocalmemory/retrieval/reranker.py +154 -0
  172. package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
  173. package/src/superlocalmemory/retrieval/strategy.py +96 -0
  174. package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
  175. package/src/superlocalmemory/server/__init__.py +1 -0
  176. package/src/superlocalmemory/server/api.py +248 -0
  177. package/src/superlocalmemory/server/routes/__init__.py +4 -0
  178. package/src/superlocalmemory/server/routes/agents.py +107 -0
  179. package/src/superlocalmemory/server/routes/backup.py +91 -0
  180. package/src/superlocalmemory/server/routes/behavioral.py +127 -0
  181. package/src/superlocalmemory/server/routes/compliance.py +160 -0
  182. package/src/superlocalmemory/server/routes/data_io.py +188 -0
  183. package/src/superlocalmemory/server/routes/events.py +183 -0
  184. package/src/superlocalmemory/server/routes/helpers.py +85 -0
  185. package/src/superlocalmemory/server/routes/learning.py +273 -0
  186. package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
  187. package/src/superlocalmemory/server/routes/memories.py +399 -0
  188. package/src/superlocalmemory/server/routes/profiles.py +219 -0
  189. package/src/superlocalmemory/server/routes/stats.py +346 -0
  190. package/src/superlocalmemory/server/routes/v3_api.py +365 -0
  191. package/src/superlocalmemory/server/routes/ws.py +82 -0
  192. package/src/superlocalmemory/server/security_middleware.py +57 -0
  193. package/src/superlocalmemory/server/ui.py +245 -0
  194. package/src/superlocalmemory/storage/__init__.py +0 -0
  195. package/src/superlocalmemory/storage/access_control.py +182 -0
  196. package/src/superlocalmemory/storage/database.py +594 -0
  197. package/src/superlocalmemory/storage/migrations.py +303 -0
  198. package/src/superlocalmemory/storage/models.py +406 -0
  199. package/src/superlocalmemory/storage/schema.py +726 -0
  200. package/src/superlocalmemory/storage/v2_migrator.py +317 -0
  201. package/src/superlocalmemory/trust/__init__.py +0 -0
  202. package/src/superlocalmemory/trust/gate.py +130 -0
  203. package/src/superlocalmemory/trust/provenance.py +124 -0
  204. package/src/superlocalmemory/trust/scorer.py +347 -0
  205. package/src/superlocalmemory/trust/signals.py +153 -0
  206. package/ui/index.html +278 -5
  207. package/ui/js/auto-settings.js +70 -0
  208. package/ui/js/dashboard.js +90 -0
  209. package/ui/js/fact-detail.js +92 -0
  210. package/ui/js/feedback.js +2 -2
  211. package/ui/js/ide-status.js +102 -0
  212. package/ui/js/math-health.js +98 -0
  213. package/ui/js/recall-lab.js +127 -0
  214. package/ui/js/settings.js +2 -2
  215. package/ui/js/trust-dashboard.js +73 -0
  216. package/api_server.py +0 -724
  217. package/bin/aider-smart +0 -72
  218. package/bin/superlocalmemoryv2-learning +0 -4
  219. package/bin/superlocalmemoryv2-list +0 -3
  220. package/bin/superlocalmemoryv2-patterns +0 -4
  221. package/bin/superlocalmemoryv2-profile +0 -3
  222. package/bin/superlocalmemoryv2-recall +0 -3
  223. package/bin/superlocalmemoryv2-remember +0 -3
  224. package/bin/superlocalmemoryv2-reset +0 -3
  225. package/bin/superlocalmemoryv2-status +0 -3
  226. package/configs/chatgpt-desktop-mcp.json +0 -16
  227. package/configs/cursor-mcp.json +0 -15
  228. package/docs/SECURITY-QUICK-REFERENCE.md +0 -214
  229. package/hooks/memory-list-skill.js +0 -139
  230. package/hooks/memory-profile-skill.js +0 -273
  231. package/hooks/memory-recall-skill.js +0 -114
  232. package/hooks/memory-remember-skill.js +0 -127
  233. package/hooks/memory-reset-skill.js +0 -274
  234. package/mcp_server.py +0 -1800
  235. package/requirements-core.txt +0 -22
  236. package/requirements-learning.txt +0 -12
  237. package/requirements.txt +0 -12
  238. package/src/agent_registry.py +0 -411
  239. package/src/auth_middleware.py +0 -61
  240. package/src/auto_backup.py +0 -459
  241. package/src/behavioral/__init__.py +0 -49
  242. package/src/behavioral/behavioral_listener.py +0 -203
  243. package/src/behavioral/behavioral_patterns.py +0 -275
  244. package/src/behavioral/cross_project_transfer.py +0 -206
  245. package/src/behavioral/outcome_inference.py +0 -194
  246. package/src/behavioral/outcome_tracker.py +0 -193
  247. package/src/behavioral/tests/__init__.py +0 -4
  248. package/src/behavioral/tests/test_behavioral_integration.py +0 -108
  249. package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
  250. package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
  251. package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
  252. package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
  253. package/src/behavioral/tests/test_outcome_inference.py +0 -107
  254. package/src/behavioral/tests/test_outcome_tracker.py +0 -96
  255. package/src/cache_manager.py +0 -518
  256. package/src/compliance/__init__.py +0 -48
  257. package/src/compliance/abac_engine.py +0 -149
  258. package/src/compliance/abac_middleware.py +0 -116
  259. package/src/compliance/audit_db.py +0 -215
  260. package/src/compliance/audit_logger.py +0 -148
  261. package/src/compliance/retention_manager.py +0 -289
  262. package/src/compliance/retention_scheduler.py +0 -186
  263. package/src/compliance/tests/__init__.py +0 -4
  264. package/src/compliance/tests/test_abac_enforcement.py +0 -95
  265. package/src/compliance/tests/test_abac_engine.py +0 -124
  266. package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
  267. package/src/compliance/tests/test_audit_db.py +0 -123
  268. package/src/compliance/tests/test_audit_logger.py +0 -98
  269. package/src/compliance/tests/test_mcp_audit.py +0 -128
  270. package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
  271. package/src/compliance/tests/test_retention_manager.py +0 -131
  272. package/src/compliance/tests/test_retention_scheduler.py +0 -99
  273. package/src/compression/__init__.py +0 -25
  274. package/src/compression/cli.py +0 -150
  275. package/src/compression/cold_storage.py +0 -217
  276. package/src/compression/config.py +0 -72
  277. package/src/compression/orchestrator.py +0 -133
  278. package/src/compression/tier2_compressor.py +0 -228
  279. package/src/compression/tier3_compressor.py +0 -153
  280. package/src/compression/tier_classifier.py +0 -148
  281. package/src/db_connection_manager.py +0 -536
  282. package/src/embedding_engine.py +0 -63
  283. package/src/embeddings/__init__.py +0 -47
  284. package/src/embeddings/cache.py +0 -70
  285. package/src/embeddings/cli.py +0 -113
  286. package/src/embeddings/constants.py +0 -47
  287. package/src/embeddings/database.py +0 -91
  288. package/src/embeddings/engine.py +0 -247
  289. package/src/embeddings/model_loader.py +0 -145
  290. package/src/event_bus.py +0 -562
  291. package/src/graph/__init__.py +0 -36
  292. package/src/graph/build_helpers.py +0 -74
  293. package/src/graph/cli.py +0 -87
  294. package/src/graph/cluster_builder.py +0 -188
  295. package/src/graph/cluster_summary.py +0 -148
  296. package/src/graph/constants.py +0 -47
  297. package/src/graph/edge_builder.py +0 -162
  298. package/src/graph/entity_extractor.py +0 -95
  299. package/src/graph/graph_core.py +0 -226
  300. package/src/graph/graph_search.py +0 -231
  301. package/src/graph/hierarchical.py +0 -207
  302. package/src/graph/schema.py +0 -99
  303. package/src/graph_engine.py +0 -52
  304. package/src/hnsw_index.py +0 -628
  305. package/src/hybrid_search.py +0 -46
  306. package/src/learning/__init__.py +0 -217
  307. package/src/learning/adaptive_ranker.py +0 -682
  308. package/src/learning/bootstrap/__init__.py +0 -69
  309. package/src/learning/bootstrap/constants.py +0 -93
  310. package/src/learning/bootstrap/db_queries.py +0 -316
  311. package/src/learning/bootstrap/sampling.py +0 -82
  312. package/src/learning/bootstrap/text_utils.py +0 -71
  313. package/src/learning/cross_project_aggregator.py +0 -857
  314. package/src/learning/db/__init__.py +0 -40
  315. package/src/learning/db/constants.py +0 -44
  316. package/src/learning/db/schema.py +0 -279
  317. package/src/learning/engagement_tracker.py +0 -628
  318. package/src/learning/feature_extractor.py +0 -708
  319. package/src/learning/feedback_collector.py +0 -806
  320. package/src/learning/learning_db.py +0 -915
  321. package/src/learning/project_context_manager.py +0 -572
  322. package/src/learning/ranking/__init__.py +0 -33
  323. package/src/learning/ranking/constants.py +0 -84
  324. package/src/learning/ranking/helpers.py +0 -278
  325. package/src/learning/source_quality_scorer.py +0 -676
  326. package/src/learning/synthetic_bootstrap.py +0 -755
  327. package/src/learning/tests/test_adaptive_ranker.py +0 -325
  328. package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
  329. package/src/learning/tests/test_aggregator.py +0 -306
  330. package/src/learning/tests/test_auto_retrain_v28.py +0 -35
  331. package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
  332. package/src/learning/tests/test_feature_extractor_v28.py +0 -93
  333. package/src/learning/tests/test_feedback_collector.py +0 -294
  334. package/src/learning/tests/test_learning_db.py +0 -602
  335. package/src/learning/tests/test_learning_db_v28.py +0 -110
  336. package/src/learning/tests/test_learning_init_v28.py +0 -48
  337. package/src/learning/tests/test_outcome_signals.py +0 -48
  338. package/src/learning/tests/test_project_context.py +0 -292
  339. package/src/learning/tests/test_schema_migration.py +0 -319
  340. package/src/learning/tests/test_signal_inference.py +0 -397
  341. package/src/learning/tests/test_source_quality.py +0 -351
  342. package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
  343. package/src/learning/tests/test_workflow_miner.py +0 -318
  344. package/src/learning/workflow_pattern_miner.py +0 -655
  345. package/src/lifecycle/__init__.py +0 -54
  346. package/src/lifecycle/bounded_growth.py +0 -239
  347. package/src/lifecycle/compaction_engine.py +0 -226
  348. package/src/lifecycle/lifecycle_engine.py +0 -355
  349. package/src/lifecycle/lifecycle_evaluator.py +0 -257
  350. package/src/lifecycle/lifecycle_scheduler.py +0 -130
  351. package/src/lifecycle/retention_policy.py +0 -285
  352. package/src/lifecycle/tests/test_bounded_growth.py +0 -193
  353. package/src/lifecycle/tests/test_compaction.py +0 -179
  354. package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
  355. package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
  356. package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
  357. package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
  358. package/src/lifecycle/tests/test_mcp_compact.py +0 -149
  359. package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
  360. package/src/lifecycle/tests/test_retention_policy.py +0 -162
  361. package/src/mcp_tools_v28.py +0 -281
  362. package/src/memory/__init__.py +0 -36
  363. package/src/memory/cli.py +0 -205
  364. package/src/memory/constants.py +0 -39
  365. package/src/memory/helpers.py +0 -28
  366. package/src/memory/schema.py +0 -166
  367. package/src/memory-profiles.py +0 -595
  368. package/src/memory-reset.py +0 -491
  369. package/src/memory_compression.py +0 -989
  370. package/src/memory_store_v2.py +0 -1155
  371. package/src/migrate_v1_to_v2.py +0 -629
  372. package/src/pattern_learner.py +0 -34
  373. package/src/patterns/__init__.py +0 -24
  374. package/src/patterns/analyzers.py +0 -251
  375. package/src/patterns/learner.py +0 -271
  376. package/src/patterns/scoring.py +0 -171
  377. package/src/patterns/store.py +0 -225
  378. package/src/patterns/terminology.py +0 -140
  379. package/src/provenance_tracker.py +0 -312
  380. package/src/qualixar_attribution.py +0 -139
  381. package/src/qualixar_watermark.py +0 -78
  382. package/src/query_optimizer.py +0 -511
  383. package/src/rate_limiter.py +0 -83
  384. package/src/search/__init__.py +0 -20
  385. package/src/search/cli.py +0 -77
  386. package/src/search/constants.py +0 -26
  387. package/src/search/engine.py +0 -241
  388. package/src/search/fusion.py +0 -122
  389. package/src/search/index_loader.py +0 -114
  390. package/src/search/methods.py +0 -162
  391. package/src/search_engine_v2.py +0 -401
  392. package/src/setup_validator.py +0 -482
  393. package/src/subscription_manager.py +0 -391
  394. package/src/tree/__init__.py +0 -59
  395. package/src/tree/builder.py +0 -185
  396. package/src/tree/nodes.py +0 -202
  397. package/src/tree/queries.py +0 -257
  398. package/src/tree/schema.py +0 -80
  399. package/src/tree_manager.py +0 -19
  400. package/src/trust/__init__.py +0 -45
  401. package/src/trust/constants.py +0 -66
  402. package/src/trust/queries.py +0 -157
  403. package/src/trust/schema.py +0 -95
  404. package/src/trust/scorer.py +0 -299
  405. package/src/trust/signals.py +0 -95
  406. package/src/trust_scorer.py +0 -44
  407. package/ui/app.js +0 -1588
  408. package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
  409. package/ui/js/graph-cytoscape.js +0 -1168
  410. package/ui/js/graph-d3-backup.js +0 -32
  411. package/ui/js/graph.js +0 -32
  412. package/ui_server.py +0 -266
  413. /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
  414. /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
  415. /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
  416. /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
  417. /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
  418. /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
  419. /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
  420. /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
  421. /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
  422. /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
  423. /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
  424. /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
  425. /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
  426. /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
  427. /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
  428. /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
  429. /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
  430. /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
  431. /package/{completions → ide/completions}/slm.bash +0 -0
  432. /package/{completions → ide/completions}/slm.zsh +0 -0
  433. /package/{configs → ide/configs}/cody-commands.json +0 -0
  434. /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
@@ -0,0 +1,65 @@
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
+
5
+ """Engine lifecycle hooks -- pre/post operation dispatch.
6
+
7
+ Pre-hooks are synchronous and can reject operations (raise exceptions).
8
+ Post-hooks are fire-and-forget -- errors are logged, never propagated.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import logging
14
+ from typing import Any, Callable
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class HookRegistry:
20
+ """Registry for engine lifecycle hooks.
21
+
22
+ Usage:
23
+ registry = HookRegistry()
24
+ registry.register_pre("store", abac_check)
25
+ registry.register_pre("store", trust_gate_check)
26
+ registry.register_post("store", audit_log)
27
+ registry.register_post("store", event_bus_publish)
28
+
29
+ # In engine.store():
30
+ registry.run_pre("store", context) # raises if any pre-hook fails
31
+ # ... core operation ...
32
+ registry.run_post("store", context) # never raises
33
+ """
34
+
35
+ def __init__(self) -> None:
36
+ self._pre: dict[str, list[Callable]] = {}
37
+ self._post: dict[str, list[Callable]] = {}
38
+
39
+ def register_pre(self, operation: str, hook: Callable[[dict[str, Any]], None]) -> None:
40
+ """Register a pre-operation hook. Hook receives context dict.
41
+ Hook can raise to reject the operation."""
42
+ self._pre.setdefault(operation, []).append(hook)
43
+
44
+ def register_post(self, operation: str, hook: Callable[[dict[str, Any]], None]) -> None:
45
+ """Register a post-operation hook. Hook receives context dict.
46
+ Errors are logged, never propagated."""
47
+ self._post.setdefault(operation, []).append(hook)
48
+
49
+ def run_pre(self, operation: str, context: dict[str, Any]) -> None:
50
+ """Run all pre-hooks for an operation. Raises on first failure."""
51
+ for hook in self._pre.get(operation, []):
52
+ hook(context) # Let exceptions propagate -- this is intentional
53
+
54
+ def run_post(self, operation: str, context: dict[str, Any]) -> None:
55
+ """Run all post-hooks for an operation. Errors logged, never raised."""
56
+ for hook in self._post.get(operation, []):
57
+ try:
58
+ hook(context)
59
+ except Exception as exc:
60
+ logger.debug("Post-hook error (%s): %s", operation, exc)
61
+
62
+ def clear(self) -> None:
63
+ """Remove all hooks. Useful for testing."""
64
+ self._pre.clear()
65
+ self._post.clear()
@@ -0,0 +1,172 @@
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
+
5
+ """SuperLocalMemory V3 — Background Math Maintenance.
6
+
7
+ Periodic batch processing for mathematical layers:
8
+ 1. Langevin batch_step on all active facts (self-organization)
9
+ 2. Sheaf batch consistency check on recent facts
10
+ 3. Fisher adaptive temperature recalculation
11
+
12
+ Frequency: every 6-24h or after 100 stores.
13
+ ~100 Langevin steps to stationarity.
14
+
15
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
16
+ License: MIT
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ from datetime import UTC, datetime, timedelta
22
+ from typing import TYPE_CHECKING
23
+
24
+ if TYPE_CHECKING:
25
+ from superlocalmemory.core.config import SLMConfig
26
+ from superlocalmemory.storage.database import DatabaseManager
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ def run_maintenance(
32
+ db: DatabaseManager,
33
+ config: SLMConfig,
34
+ profile_id: str = "default",
35
+ ) -> dict[str, int]:
36
+ """Run background maintenance on mathematical layers.
37
+
38
+ Args:
39
+ db: Database manager.
40
+ config: Full SLM configuration.
41
+ profile_id: Scope to this profile.
42
+
43
+ Returns:
44
+ Dict of counts: langevin_updated, sheaf_checked, etc.
45
+ """
46
+ counts: dict[str, int] = {
47
+ "langevin_updated": 0,
48
+ "fisher_coupled": 0,
49
+ "sheaf_checked": 0,
50
+ }
51
+
52
+ facts = db.get_all_facts(profile_id)
53
+ if not facts:
54
+ return counts
55
+
56
+ # 1. Langevin batch step
57
+ if config.math.langevin_persist_positions:
58
+ try:
59
+ from superlocalmemory.math.langevin import LangevinDynamics
60
+
61
+ ld = LangevinDynamics(
62
+ dim=8,
63
+ dt=config.math.langevin_dt,
64
+ temperature=config.math.langevin_temperature,
65
+ )
66
+ fact_dicts = []
67
+ for f in facts:
68
+ if f.langevin_position is None:
69
+ continue
70
+ created = datetime.fromisoformat(
71
+ f.created_at.replace("Z", "+00:00")
72
+ ) if f.created_at else datetime.now(UTC)
73
+ age_days = max(
74
+ 0.0,
75
+ (datetime.now(UTC) - created).total_seconds() / 86400.0,
76
+ )
77
+ fact_dicts.append({
78
+ "fact_id": f.fact_id,
79
+ "position": f.langevin_position,
80
+ "access_count": f.access_count,
81
+ "age_days": age_days,
82
+ "importance": f.importance,
83
+ })
84
+
85
+ if fact_dicts:
86
+ results = ld.batch_step(fact_dicts)
87
+ for r in results:
88
+ db.update_fact(r["fact_id"], {
89
+ "langevin_position": r["position"],
90
+ "lifecycle": r["lifecycle"],
91
+ })
92
+ counts["langevin_updated"] = len(results)
93
+ except Exception as exc:
94
+ logger.warning("Langevin maintenance failed: %s", exc)
95
+
96
+ # 1b. Fisher-Langevin coupling: modulate temperature per-fact
97
+ # High Fisher confidence (low variance) -> low temperature -> memory stabilizes
98
+ # Low Fisher confidence (high variance) -> high temperature -> memory fades
99
+ if config.math.langevin_persist_positions and counts["langevin_updated"] > 0:
100
+ try:
101
+ from superlocalmemory.dynamics.fisher_langevin_coupling import (
102
+ FisherLangevinCoupling,
103
+ )
104
+
105
+ coupling = FisherLangevinCoupling(
106
+ base_temperature=config.math.langevin_temperature,
107
+ )
108
+ coupled_count = 0
109
+
110
+ for f in facts:
111
+ if f.langevin_position is None or f.fisher_variance is None:
112
+ continue
113
+ eff_temp = coupling.get_effective_temperature(
114
+ f.fisher_variance, f.access_count,
115
+ )
116
+ # Re-run Langevin step with Fisher-coupled temperature
117
+ # only if it differs meaningfully from the base temperature
118
+ if abs(eff_temp - config.math.langevin_temperature) > 0.01:
119
+ from superlocalmemory.math.langevin import LangevinDynamics
120
+
121
+ coupled_ld = LangevinDynamics(
122
+ dim=8,
123
+ dt=config.math.langevin_dt,
124
+ temperature=eff_temp,
125
+ )
126
+ created = datetime.fromisoformat(
127
+ f.created_at.replace("Z", "+00:00")
128
+ ) if f.created_at else datetime.now(UTC)
129
+ age_days = max(
130
+ 0.0,
131
+ (datetime.now(UTC) - created).total_seconds() / 86400.0,
132
+ )
133
+ new_pos, weight = coupled_ld.step(
134
+ position=f.langevin_position,
135
+ access_count=f.access_count,
136
+ age_days=age_days,
137
+ importance=f.importance,
138
+ )
139
+ lifecycle = coupled_ld.get_lifecycle_state(weight).value
140
+ db.update_fact(f.fact_id, {
141
+ "langevin_position": new_pos,
142
+ "lifecycle": lifecycle,
143
+ })
144
+ coupled_count += 1
145
+
146
+ counts["fisher_coupled"] = coupled_count
147
+ except Exception as exc:
148
+ logger.warning("Fisher-Langevin coupling failed: %s", exc)
149
+
150
+ # 2. Sheaf batch consistency on recent facts (last 24h)
151
+ if config.math.sheaf_at_encoding:
152
+ try:
153
+ from superlocalmemory.math.sheaf import SheafConsistencyChecker
154
+
155
+ checker = SheafConsistencyChecker(
156
+ db, config.math.sheaf_contradiction_threshold,
157
+ )
158
+ cutoff = (datetime.now(UTC) - timedelta(hours=24)).isoformat()
159
+ recent = [f for f in facts if f.created_at and f.created_at >= cutoff]
160
+ for f in recent:
161
+ if f.embedding and f.canonical_entities:
162
+ checker.check_consistency(f, profile_id)
163
+ counts["sheaf_checked"] += 1
164
+ except Exception as exc:
165
+ logger.warning("Sheaf maintenance failed: %s", exc)
166
+
167
+ logger.info(
168
+ "Maintenance complete: %d Langevin, %d Fisher-coupled, %d Sheaf",
169
+ counts["langevin_updated"], counts["fisher_coupled"],
170
+ counts["sheaf_checked"],
171
+ )
172
+ return counts
@@ -0,0 +1,140 @@
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
+
5
+ """SuperLocalMemory V3 — Mode System.
6
+
7
+ Three operating modes with clear capability boundaries.
8
+ Mode A: EU AI Act FULL compliance (zero LLM).
9
+ Mode B: EU AI Act FULL (local LLM only).
10
+ Mode C: UNRESTRICTED — best models, full power, 90%+ target.
11
+
12
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from dataclasses import dataclass
18
+
19
+ from superlocalmemory.storage.models import Mode
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class ModeCapabilities:
24
+ """What each mode can and cannot do."""
25
+
26
+ mode: Mode
27
+
28
+ # Encoding capabilities
29
+ llm_fact_extraction: bool # Can use LLM for fact extraction?
30
+ llm_entity_resolution: bool # Can use LLM for entity disambiguation?
31
+ llm_type_classification: bool # Can use LLM for fact type routing?
32
+ llm_importance_scoring: bool # Can use LLM for importance assessment?
33
+
34
+ # Retrieval capabilities
35
+ agentic_retrieval: bool # Can do multi-round LLM-guided retrieval?
36
+ llm_answer_generation: bool # Can LLM generate answers from context?
37
+ cloud_reranker: bool # Can use Cohere / cloud reranker?
38
+
39
+ # Embedding capabilities
40
+ cloud_embeddings: bool # Can use cloud embedding API?
41
+ embedding_dimension: int # Expected embedding dimension
42
+
43
+ # Compliance
44
+ eu_ai_act_compliant: bool # Full EU AI Act compliance?
45
+ data_stays_local: bool # Does ALL data stay on device?
46
+
47
+ # Description
48
+ description: str = ""
49
+
50
+
51
+ # ---------------------------------------------------------------------------
52
+ # Mode Definitions
53
+ # ---------------------------------------------------------------------------
54
+
55
+ MODE_A = ModeCapabilities(
56
+ mode=Mode.A,
57
+ llm_fact_extraction=False,
58
+ llm_entity_resolution=False,
59
+ llm_type_classification=False,
60
+ llm_importance_scoring=False,
61
+ agentic_retrieval=False,
62
+ llm_answer_generation=False,
63
+ cloud_reranker=False,
64
+ cloud_embeddings=False,
65
+ embedding_dimension=768,
66
+ eu_ai_act_compliant=True,
67
+ data_stays_local=True,
68
+ description=(
69
+ "Local Guardian — Zero LLM, zero cloud. "
70
+ "Uses nomic-embed-text-v1.5 encoder (768d, 8K context) for embeddings. "
71
+ "spaCy + rules for extraction. Cross-encoder for reranking. "
72
+ "Full EU AI Act compliance. Target: 65%+"
73
+ ),
74
+ )
75
+
76
+ MODE_B = ModeCapabilities(
77
+ mode=Mode.B,
78
+ llm_fact_extraction=True,
79
+ llm_entity_resolution=True,
80
+ llm_type_classification=True,
81
+ llm_importance_scoring=True,
82
+ agentic_retrieval=False,
83
+ llm_answer_generation=True,
84
+ cloud_reranker=False,
85
+ cloud_embeddings=False,
86
+ embedding_dimension=768,
87
+ eu_ai_act_compliant=True,
88
+ data_stays_local=True,
89
+ description=(
90
+ "Smart Local — Local Ollama LLM (Phi-3, Llama 3.2). "
91
+ "LLM-quality extraction and classification, fully local. "
92
+ "No cloud, no data export. EU AI Act compliant. Target: 75-80%"
93
+ ),
94
+ )
95
+
96
+ MODE_C = ModeCapabilities(
97
+ mode=Mode.C,
98
+ llm_fact_extraction=True,
99
+ llm_entity_resolution=True,
100
+ llm_type_classification=True,
101
+ llm_importance_scoring=True,
102
+ agentic_retrieval=True,
103
+ llm_answer_generation=True,
104
+ cloud_reranker=True,
105
+ cloud_embeddings=True,
106
+ embedding_dimension=3072,
107
+ eu_ai_act_compliant=False,
108
+ data_stays_local=False,
109
+ description=(
110
+ "FULL POWER — UNRESTRICTED. Best embeddings (text-embedding-3-large, 3072-dim). "
111
+ "Best LLMs (GPT-5.2, Claude Opus). Agentic multi-round retrieval. "
112
+ "Cohere reranker option. No EU restriction. Target: 90%+"
113
+ ),
114
+ )
115
+
116
+
117
+ def get_capabilities(mode: Mode) -> ModeCapabilities:
118
+ """Get capability matrix for a mode."""
119
+ _map = {Mode.A: MODE_A, Mode.B: MODE_B, Mode.C: MODE_C}
120
+ return _map[mode]
121
+
122
+
123
+ def validate_mode_config(mode: Mode, *, has_ollama: bool = False, has_cloud_llm: bool = False) -> list[str]:
124
+ """Validate that required services are available for the chosen mode.
125
+
126
+ Returns list of warnings/errors. Empty list = all good.
127
+ """
128
+ issues: list[str] = []
129
+ caps = get_capabilities(mode)
130
+
131
+ if caps.llm_fact_extraction and mode == Mode.B and not has_ollama:
132
+ issues.append("Mode B requires Ollama but it is not available. Falling back to Mode A extraction.")
133
+
134
+ if caps.cloud_embeddings and not has_cloud_llm:
135
+ issues.append("Mode C cloud embeddings configured but no API endpoint provided.")
136
+
137
+ if caps.agentic_retrieval and not has_cloud_llm:
138
+ issues.append("Mode C agentic retrieval requires cloud LLM but none configured.")
139
+
140
+ return issues
@@ -0,0 +1,234 @@
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
+
5
+ """SuperLocalMemory V3 — Profile Management.
6
+
7
+ First-class profile isolation. Every memory, fact, entity, and learning
8
+ record is scoped by profile_id. Profiles are persisted in profiles.json
9
+ with atomic writes and instant switching (config-only, zero data movement).
10
+
11
+ Ported from V2.8 columnar profile isolation pattern.
12
+
13
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import logging
20
+ import os
21
+ import re
22
+ import tempfile
23
+ import uuid
24
+ from dataclasses import asdict
25
+ from datetime import datetime, timezone
26
+ from pathlib import Path
27
+ from typing import Any, Callable
28
+
29
+ from superlocalmemory.core.config import DEFAULT_PROFILES_FILE
30
+ from superlocalmemory.storage.models import Mode, Profile
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+ _VALID_NAME_RE = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,49}$")
35
+ _MAX_NAME_LEN = 50
36
+ _RESERVED = "default"
37
+
38
+
39
+ def _validate_name(name: str) -> None:
40
+ """Alphanumeric + dash + underscore, 1-50 chars, starts alphanumeric."""
41
+ if not name or len(name) > _MAX_NAME_LEN:
42
+ raise ValueError(f"Profile name must be 1-{_MAX_NAME_LEN} chars, got {len(name)}.")
43
+ if not _VALID_NAME_RE.match(name):
44
+ raise ValueError(
45
+ f"Invalid profile name '{name}'. "
46
+ "Must start with alphanumeric, then alphanumeric / dash / underscore."
47
+ )
48
+
49
+
50
+ def _now() -> str:
51
+ return datetime.now(timezone.utc).isoformat()
52
+
53
+
54
+ def _pid() -> str:
55
+ return uuid.uuid4().hex[:16]
56
+
57
+
58
+ def _evolve(prof: Profile, **overrides: Any) -> Profile:
59
+ """Return a new Profile with selected fields replaced (frozen dataclass)."""
60
+ base = asdict(prof)
61
+ base.update(overrides)
62
+ base["mode"] = Mode(base["mode"]) if isinstance(base["mode"], str) else base["mode"]
63
+ return Profile(**base)
64
+
65
+
66
+ class ProfileManager:
67
+ """Thread-safe profile manager with atomic JSON persistence.
68
+
69
+ Stores profile metadata in ``profiles.json`` inside *base_dir*.
70
+ Profiles are isolated at the DB column level (WHERE profile_id = ?).
71
+ Switching is instant — only the active pointer changes.
72
+ """
73
+
74
+ def __init__(self, base_dir: Path) -> None:
75
+ self._base_dir = base_dir
76
+ self._base_dir.mkdir(parents=True, exist_ok=True)
77
+ self._path = self._base_dir / DEFAULT_PROFILES_FILE
78
+ self._profiles: dict[str, Profile] = {}
79
+ self._active_name: str = _RESERVED
80
+ self._on_switch: Callable[[Profile], None] | None = None
81
+ self._load()
82
+
83
+ # -- Persistence (atomic write via tempfile + rename) ------------------
84
+
85
+ def _load(self) -> None:
86
+ """Load profiles.json or bootstrap with the default profile."""
87
+ if self._path.exists():
88
+ raw = json.loads(self._path.read_text(encoding="utf-8"))
89
+ self._active_name = raw.get("active", _RESERVED)
90
+ for entry in raw.get("profiles", []):
91
+ mode = Mode(entry["mode"]) if "mode" in entry else Mode.A
92
+ prof = Profile(
93
+ profile_id=entry["profile_id"],
94
+ name=entry["name"],
95
+ description=entry.get("description", ""),
96
+ personality=entry.get("personality", ""),
97
+ mode=mode,
98
+ created_at=entry.get("created_at", _now()),
99
+ last_used=entry.get("last_used"),
100
+ config=entry.get("config", {}),
101
+ )
102
+ self._profiles[prof.name] = prof
103
+ if _RESERVED not in self._profiles:
104
+ self._profiles[_RESERVED] = Profile(
105
+ profile_id=_pid(), name=_RESERVED,
106
+ description="Default memory profile", mode=Mode.A,
107
+ created_at=_now(),
108
+ )
109
+ self._save()
110
+
111
+ def _save(self) -> None:
112
+ """Atomic write: temp file in same dir, then rename."""
113
+ data = json.dumps(
114
+ {"active": self._active_name,
115
+ "profiles": [asdict(p) for p in self._profiles.values()]},
116
+ indent=2, ensure_ascii=False,
117
+ )
118
+ fd, tmp = tempfile.mkstemp(dir=str(self._base_dir), suffix=".tmp")
119
+ try:
120
+ Path(tmp).write_text(data, encoding="utf-8")
121
+ Path(tmp).replace(self._path)
122
+ finally:
123
+ try:
124
+ os.close(fd)
125
+ except OSError:
126
+ pass
127
+
128
+ # -- Event hook --------------------------------------------------------
129
+
130
+ @property
131
+ def on_profile_switch(self) -> Callable[[Profile], None] | None:
132
+ return self._on_switch
133
+
134
+ @on_profile_switch.setter
135
+ def on_profile_switch(self, cb: Callable[[Profile], None] | None) -> None:
136
+ self._on_switch = cb
137
+
138
+ # -- CRUD --------------------------------------------------------------
139
+
140
+ def create_profile(
141
+ self,
142
+ name: str,
143
+ description: str = "",
144
+ personality: str = "",
145
+ mode: Mode = Mode.A,
146
+ config: dict[str, Any] | None = None,
147
+ ) -> Profile:
148
+ """Create a new profile. Raises ValueError on duplicate or bad name."""
149
+ _validate_name(name)
150
+ if name in self._profiles:
151
+ raise ValueError(f"Profile '{name}' already exists.")
152
+ profile = Profile(
153
+ profile_id=_pid(), name=name, description=description,
154
+ personality=personality, mode=mode,
155
+ created_at=_now(), config=config or {},
156
+ )
157
+ self._profiles[name] = profile
158
+ self._save()
159
+ logger.info("Created profile '%s' (id=%s, mode=%s)", name, profile.profile_id, mode.value)
160
+ return profile
161
+
162
+ def get_profile(self, name: str) -> Profile | None:
163
+ """Return profile by name, or None if not found."""
164
+ return self._profiles.get(name)
165
+
166
+ def get_active_profile(self) -> Profile:
167
+ """Return the currently active profile."""
168
+ return self._profiles.get(self._active_name) or self._profiles[_RESERVED]
169
+
170
+ def switch_profile(self, name: str) -> Profile:
171
+ """Instant switch — config-only, zero data movement."""
172
+ if name not in self._profiles:
173
+ raise KeyError(f"Profile '{name}' does not exist.")
174
+ self._active_name = name
175
+ updated = _evolve(self._profiles[name], last_used=_now())
176
+ self._profiles[name] = updated
177
+ self._save()
178
+ logger.info("Switched to profile '%s'", name)
179
+ if self._on_switch is not None:
180
+ self._on_switch(updated)
181
+ return updated
182
+
183
+ def list_profiles(self) -> list[Profile]:
184
+ """All profiles sorted by name (default first)."""
185
+ profs = list(self._profiles.values())
186
+ profs.sort(key=lambda p: (p.name != _RESERVED, p.name))
187
+ return profs
188
+
189
+ def delete_profile(self, name: str) -> None:
190
+ """Delete a profile. Cannot delete 'default'. Data stays in DB."""
191
+ if name == _RESERVED:
192
+ raise ValueError("Cannot delete the default profile.")
193
+ if name not in self._profiles:
194
+ raise KeyError(f"Profile '{name}' does not exist.")
195
+ del self._profiles[name]
196
+ if self._active_name == name:
197
+ self._active_name = _RESERVED
198
+ logger.info("Active profile deleted — fell back to 'default'.")
199
+ self._save()
200
+ logger.info("Deleted profile '%s'. Associated data remains in DB.", name)
201
+
202
+ def rename_profile(self, old_name: str, new_name: str) -> None:
203
+ """Rename a profile. Cannot rename 'default'."""
204
+ if old_name == _RESERVED:
205
+ raise ValueError("Cannot rename the default profile.")
206
+ _validate_name(new_name)
207
+ if old_name not in self._profiles:
208
+ raise KeyError(f"Profile '{old_name}' does not exist.")
209
+ if new_name in self._profiles:
210
+ raise ValueError(f"Profile '{new_name}' already exists.")
211
+ old = self._profiles.pop(old_name)
212
+ self._profiles[new_name] = _evolve(old, name=new_name)
213
+ if self._active_name == old_name:
214
+ self._active_name = new_name
215
+ self._save()
216
+ logger.info("Renamed profile '%s' -> '%s'", old_name, new_name)
217
+
218
+ def update_profile(self, name: str, **kwargs: Any) -> Profile:
219
+ """Update mutable fields: description, personality, mode, config."""
220
+ if name not in self._profiles:
221
+ raise KeyError(f"Profile '{name}' does not exist.")
222
+ allowed = {"description", "personality", "mode", "config"}
223
+ overrides = {k: v for k, v in kwargs.items() if k in allowed}
224
+ updated = _evolve(self._profiles[name], **overrides)
225
+ self._profiles[name] = updated
226
+ self._save()
227
+ logger.info("Updated profile '%s'", name)
228
+ return updated
229
+
230
+ def export_profile(self, name: str) -> dict[str, Any]:
231
+ """Export profile metadata as a plain dict (for backup / migration)."""
232
+ if name not in self._profiles:
233
+ raise KeyError(f"Profile '{name}' does not exist.")
234
+ return asdict(self._profiles[name])