superlocalmemory 2.8.6 → 3.0.1

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