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,91 @@
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 — Foresight Signal Extraction.
6
+
7
+ Extracts time-bounded intentions and planned events from conversations.
8
+ V1 extracted foresight signals but NEVER STORED them. Now persisted as TemporalEvents.
9
+
10
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import logging
16
+ import re
17
+ from datetime import datetime
18
+
19
+ from superlocalmemory.storage.models import AtomicFact, TemporalEvent
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ # Patterns that indicate future intent or planned events
24
+ _FORESIGHT_PATTERNS = [
25
+ re.compile(r"\b(plan(?:ning|s|ned)?)\s+to\b", re.I),
26
+ re.compile(r"\b(going)\s+to\b", re.I),
27
+ re.compile(r"\b(will|shall)\s+\w+", re.I),
28
+ re.compile(r"\b(schedul(?:e|ed|ing))\b", re.I),
29
+ re.compile(r"\b(appointment|reservation|booking)\b", re.I),
30
+ re.compile(r"\b(remind(?:er)?|don't forget)\b", re.I),
31
+ re.compile(r"\b(taking|starting|beginning)\s+\w+\s+(next|tomorrow|soon)\b", re.I),
32
+ re.compile(r"\b(deadline|due date|due by)\b", re.I),
33
+ re.compile(r"\bnext\s+(week|month|year|monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b", re.I),
34
+ ]
35
+
36
+
37
+ def extract_foresight_signals(fact: AtomicFact) -> list[dict]:
38
+ """Extract foresight signals from a fact's content.
39
+
40
+ Returns list of dicts with: content, pattern_matched.
41
+ These get converted to TemporalEvents by the encoding pipeline.
42
+ """
43
+ signals = []
44
+ for pattern in _FORESIGHT_PATTERNS:
45
+ match = pattern.search(fact.content)
46
+ if match:
47
+ signals.append({
48
+ "content": fact.content,
49
+ "pattern": match.group(0),
50
+ "fact_id": fact.fact_id,
51
+ })
52
+ break # One signal per fact is sufficient
53
+
54
+ return signals
55
+
56
+
57
+ def foresight_to_temporal_events(
58
+ fact: AtomicFact,
59
+ entity_ids: list[str],
60
+ profile_id: str,
61
+ ) -> list[TemporalEvent]:
62
+ """Convert foresight signals into TemporalEvents for persistence.
63
+
64
+ Each signal becomes a temporal event linked to the fact's entities.
65
+ The temporal_parser handles actual date extraction — we just mark
66
+ the fact as having foresight intent.
67
+ """
68
+ signals = extract_foresight_signals(fact)
69
+ if not signals:
70
+ return []
71
+
72
+ events = []
73
+ for eid in entity_ids:
74
+ event = TemporalEvent(
75
+ profile_id=profile_id,
76
+ entity_id=eid,
77
+ fact_id=fact.fact_id,
78
+ observation_date=fact.observation_date,
79
+ referenced_date=fact.referenced_date,
80
+ interval_start=fact.interval_start,
81
+ interval_end=fact.interval_end,
82
+ description=f"Foresight: {fact.content[:200]}",
83
+ )
84
+ events.append(event)
85
+
86
+ return events
87
+
88
+
89
+ def has_foresight(text: str) -> bool:
90
+ """Quick check if text contains foresight signals."""
91
+ return any(p.search(text) for p in _FORESIGHT_PATTERNS)
@@ -0,0 +1,302 @@
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
+ """Knowledge graph construction — 4 edge types, zero memory limits.
6
+
7
+ Builds edges between AtomicFacts at encoding time. V1 only checked the
8
+ last 50 memories; this version searches ALL facts per canonical entity.
9
+
10
+ Edge types: ENTITY (shared entity, weight 1.0), TEMPORAL (exp-decay,
11
+ 1-week window), SEMANTIC (ANN cosine > 0.7), CAUSAL (causal markers,
12
+ weight 0.8). CONTRADICTION exposed for external Sheaf module (Wave 4).
13
+
14
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
15
+ License: MIT
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import logging
20
+ import math
21
+ import re
22
+ from datetime import datetime
23
+ from typing import Any, Protocol, runtime_checkable
24
+
25
+ from superlocalmemory.storage.database import DatabaseManager
26
+ from superlocalmemory.storage.models import AtomicFact, EdgeType, GraphEdge
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ # Constants
31
+ _TEMPORAL_MAX_HOURS: float = 168.0 # 1 week
32
+ _TEMPORAL_DECAY_DAYS: float = 30.0 # exp(-delta_days / 30)
33
+ _SEMANTIC_THRESHOLD: float = 0.7
34
+ _SEMANTIC_TOP_K: int = 5
35
+ _CAUSAL_WEIGHT: float = 0.8
36
+ _ENTITY_WEIGHT: float = 1.0
37
+
38
+ # Causal cue patterns — longer phrases first to avoid partial matches.
39
+ _CAUSAL_CUES: tuple[re.Pattern[str], ...] = (
40
+ re.compile(r"\bbecause of\b", re.I),
41
+ re.compile(r"\bas a result\b", re.I),
42
+ re.compile(r"\bresulted in\b", re.I),
43
+ re.compile(r"\bin order to\b", re.I),
44
+ re.compile(r"\bso that\b", re.I),
45
+ re.compile(r"\bcaused by\b", re.I),
46
+ re.compile(r"\bdue to\b", re.I),
47
+ re.compile(r"\bled to\b", re.I),
48
+ re.compile(r"\btherefore\b", re.I),
49
+ re.compile(r"\bconsequently\b", re.I),
50
+ re.compile(r"\bbecause\b", re.I),
51
+ )
52
+
53
+
54
+ @runtime_checkable
55
+ class ANNSearchable(Protocol):
56
+ """Minimal protocol for approximate-nearest-neighbor search."""
57
+
58
+ def search(self, query: Any, top_k: int = 5) -> list[tuple[str, float]]:
59
+ """Return (fact_id, similarity_score) pairs."""
60
+ ... # pragma: no cover
61
+
62
+
63
+ class GraphBuilder:
64
+ """Build knowledge-graph edges for newly stored facts.
65
+
66
+ Searches the ENTIRE corpus per canonical entity (not just last 50).
67
+ Adds semantic similarity edges via optional ANN index and detects
68
+ causal language for directed cause/effect edges.
69
+ """
70
+
71
+ def __init__(self, db: DatabaseManager, ann_index: ANNSearchable | None = None) -> None:
72
+ self._db = db
73
+ self._ann = ann_index
74
+
75
+ # -- Public API --------------------------------------------------------
76
+
77
+ def build_edges(self, new_fact: AtomicFact, profile_id: str) -> list[GraphEdge]:
78
+ """Create ALL relevant edges for *new_fact*. Persists and returns them."""
79
+ edges: list[GraphEdge] = []
80
+ edges.extend(self._build_entity_edges(new_fact, profile_id))
81
+ edges.extend(self._build_temporal_edges(new_fact, profile_id))
82
+ edges.extend(self._build_semantic_edges(new_fact, profile_id))
83
+ edges.extend(self._build_causal_edges(new_fact, profile_id))
84
+
85
+ for edge in edges:
86
+ self._db.store_edge(edge)
87
+
88
+ if edges:
89
+ logger.debug(
90
+ "GraphBuilder: %d edges for %s (E=%d T=%d S=%d C=%d)",
91
+ len(edges), new_fact.fact_id,
92
+ sum(1 for e in edges if e.edge_type == EdgeType.ENTITY),
93
+ sum(1 for e in edges if e.edge_type == EdgeType.TEMPORAL),
94
+ sum(1 for e in edges if e.edge_type == EdgeType.SEMANTIC),
95
+ sum(1 for e in edges if e.edge_type == EdgeType.CAUSAL),
96
+ )
97
+ return edges
98
+
99
+ def add_contradiction_edge(
100
+ self, fact_id_a: str, fact_id_b: str, profile_id: str,
101
+ severity: float = 1.0,
102
+ ) -> GraphEdge:
103
+ """Add a contradiction edge. Called by Sheaf module (Wave 4)."""
104
+ edge = GraphEdge(
105
+ profile_id=profile_id,
106
+ source_id=fact_id_a,
107
+ target_id=fact_id_b,
108
+ edge_type=EdgeType.CONTRADICTION,
109
+ weight=max(0.0, min(1.0, severity)),
110
+ )
111
+ self._db.store_edge(edge)
112
+ logger.info("Contradiction %s -> %s (%.2f)", fact_id_a, fact_id_b, severity)
113
+ return edge
114
+
115
+ def get_graph_stats(self, profile_id: str) -> dict[str, Any]:
116
+ """Edge counts by type, node count, average degree."""
117
+ rows = self._db.execute(
118
+ "SELECT edge_type, COUNT(*) AS cnt FROM graph_edges "
119
+ "WHERE profile_id = ? GROUP BY edge_type", (profile_id,),
120
+ )
121
+ edge_counts: dict[str, int] = {
122
+ dict(r)["edge_type"]: int(dict(r)["cnt"]) for r in rows
123
+ }
124
+ total_edges = sum(edge_counts.values())
125
+
126
+ node_rows = self._db.execute(
127
+ "SELECT COUNT(DISTINCT n) AS c FROM ("
128
+ " SELECT source_id AS n FROM graph_edges WHERE profile_id = ? "
129
+ " UNION "
130
+ " SELECT target_id AS n FROM graph_edges WHERE profile_id = ?"
131
+ ")", (profile_id, profile_id),
132
+ )
133
+ node_count = int(dict(node_rows[0])["c"]) if node_rows else 0
134
+ avg_degree = (2.0 * total_edges / node_count) if node_count > 0 else 0.0
135
+
136
+ return {
137
+ "edge_counts": edge_counts,
138
+ "total_edges": total_edges,
139
+ "node_count": node_count,
140
+ "avg_degree": round(avg_degree, 2),
141
+ }
142
+
143
+ # -- Edge builders (private) -------------------------------------------
144
+
145
+ def _build_entity_edges(
146
+ self, new_fact: AtomicFact, profile_id: str,
147
+ ) -> list[GraphEdge]:
148
+ """ENTITY edges: shared canonical entity — NO 50-memory limit."""
149
+ if not new_fact.canonical_entities:
150
+ return []
151
+ edges: list[GraphEdge] = []
152
+ seen: set[str] = set()
153
+
154
+ for entity_id in new_fact.canonical_entities:
155
+ for other in self._db.get_facts_by_entity(entity_id, profile_id):
156
+ if other.fact_id == new_fact.fact_id or other.fact_id in seen:
157
+ continue
158
+ if self._edge_exists(new_fact.fact_id, other.fact_id, EdgeType.ENTITY, profile_id):
159
+ continue
160
+ seen.add(other.fact_id)
161
+ edges.append(GraphEdge(
162
+ profile_id=profile_id, source_id=new_fact.fact_id,
163
+ target_id=other.fact_id, edge_type=EdgeType.ENTITY,
164
+ weight=_ENTITY_WEIGHT,
165
+ ))
166
+ return edges
167
+
168
+ def _build_temporal_edges(
169
+ self, new_fact: AtomicFact, profile_id: str,
170
+ ) -> list[GraphEdge]:
171
+ """TEMPORAL edges: bidirectional, exp-decay, 1-week window per entity.
172
+
173
+ Only creates temporal edges when an explicit observation_date is set.
174
+ Falling back to created_at would produce spurious temporal edges for
175
+ facts that have no real temporal context.
176
+ """
177
+ if not new_fact.observation_date or not new_fact.canonical_entities:
178
+ return []
179
+ new_dt = _parse_date(new_fact.observation_date)
180
+ if new_dt is None:
181
+ return []
182
+
183
+ edges: list[GraphEdge] = []
184
+ seen_pairs: set[tuple[str, str]] = set()
185
+
186
+ for entity_id in new_fact.canonical_entities:
187
+ for other in self._db.get_facts_by_entity(entity_id, profile_id):
188
+ if other.fact_id == new_fact.fact_id:
189
+ continue
190
+ other_dt = _parse_date(other.observation_date)
191
+ if other_dt is None:
192
+ continue
193
+
194
+ delta_hours = abs((new_dt - other_dt).total_seconds()) / 3600.0
195
+ if delta_hours > _TEMPORAL_MAX_HOURS:
196
+ continue
197
+
198
+ pair_key = (min(new_fact.fact_id, other.fact_id),
199
+ max(new_fact.fact_id, other.fact_id))
200
+ if pair_key in seen_pairs:
201
+ continue
202
+ if self._edge_exists(new_fact.fact_id, other.fact_id, EdgeType.TEMPORAL, profile_id):
203
+ seen_pairs.add(pair_key)
204
+ continue
205
+
206
+ weight = round(max(math.exp(-(delta_hours / 24.0) / _TEMPORAL_DECAY_DAYS), 0.01), 4)
207
+ seen_pairs.add(pair_key)
208
+
209
+ # Forward: new -> other
210
+ edges.append(GraphEdge(
211
+ profile_id=profile_id, source_id=new_fact.fact_id,
212
+ target_id=other.fact_id, edge_type=EdgeType.TEMPORAL,
213
+ weight=weight,
214
+ ))
215
+ # Reverse: other -> new
216
+ if not self._edge_exists(other.fact_id, new_fact.fact_id, EdgeType.TEMPORAL, profile_id):
217
+ edges.append(GraphEdge(
218
+ profile_id=profile_id, source_id=other.fact_id,
219
+ target_id=new_fact.fact_id, edge_type=EdgeType.TEMPORAL,
220
+ weight=weight,
221
+ ))
222
+ return edges
223
+
224
+ def _build_semantic_edges(
225
+ self, new_fact: AtomicFact, profile_id: str,
226
+ ) -> list[GraphEdge]:
227
+ """SEMANTIC edges: ANN embedding similarity > 0.7 threshold."""
228
+ if self._ann is None or new_fact.embedding is None:
229
+ return []
230
+ try:
231
+ import numpy as np
232
+ query_vec = np.asarray(new_fact.embedding, dtype=np.float32)
233
+ except (ImportError, ValueError):
234
+ return []
235
+
236
+ edges: list[GraphEdge] = []
237
+ for fact_id, score in self._ann.search(query_vec, top_k=_SEMANTIC_TOP_K + 1):
238
+ if fact_id == new_fact.fact_id or score < _SEMANTIC_THRESHOLD:
239
+ continue
240
+ if self._edge_exists(new_fact.fact_id, fact_id, EdgeType.SEMANTIC, profile_id):
241
+ continue
242
+ edges.append(GraphEdge(
243
+ profile_id=profile_id, source_id=new_fact.fact_id,
244
+ target_id=fact_id, edge_type=EdgeType.SEMANTIC,
245
+ weight=round(float(score), 4),
246
+ ))
247
+ if len(edges) >= _SEMANTIC_TOP_K:
248
+ break
249
+ return edges
250
+
251
+ def _build_causal_edges(
252
+ self, new_fact: AtomicFact, profile_id: str,
253
+ ) -> list[GraphEdge]:
254
+ """CAUSAL edges: causal markers + shared entity. Direction: cause -> effect."""
255
+ if not any(p.search(new_fact.content) for p in _CAUSAL_CUES):
256
+ return []
257
+ if not new_fact.canonical_entities:
258
+ return []
259
+
260
+ edges: list[GraphEdge] = []
261
+ seen: set[str] = set()
262
+ for entity_id in new_fact.canonical_entities:
263
+ for other in self._db.get_facts_by_entity(entity_id, profile_id):
264
+ if other.fact_id == new_fact.fact_id or other.fact_id in seen:
265
+ continue
266
+ if self._edge_exists(other.fact_id, new_fact.fact_id, EdgeType.CAUSAL, profile_id):
267
+ continue
268
+ seen.add(other.fact_id)
269
+ edges.append(GraphEdge(
270
+ profile_id=profile_id, source_id=other.fact_id,
271
+ target_id=new_fact.fact_id, edge_type=EdgeType.CAUSAL,
272
+ weight=_CAUSAL_WEIGHT,
273
+ ))
274
+ return edges
275
+
276
+ # -- Helpers -----------------------------------------------------------
277
+
278
+ def _edge_exists(
279
+ self, source_id: str, target_id: str,
280
+ edge_type: EdgeType, profile_id: str,
281
+ ) -> bool:
282
+ """Check if an edge already exists (prevents duplicates)."""
283
+ rows = self._db.execute(
284
+ "SELECT 1 FROM graph_edges "
285
+ "WHERE profile_id = ? AND source_id = ? AND target_id = ? "
286
+ "AND edge_type = ? LIMIT 1",
287
+ (profile_id, source_id, target_id, edge_type.value),
288
+ )
289
+ return len(rows) > 0
290
+
291
+
292
+ def _parse_date(raw: str | None) -> datetime | None:
293
+ """Best-effort ISO-8601 datetime parse."""
294
+ if not raw:
295
+ return None
296
+ for fmt in ("%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S",
297
+ "%Y-%m-%d %H:%M:%S", "%Y-%m-%d"):
298
+ try:
299
+ return datetime.strptime(raw, fmt)
300
+ except ValueError:
301
+ continue
302
+ return None
@@ -0,0 +1,160 @@
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 — Observation Builder (Entity Profiles).
6
+
7
+ Builds and updates accumulated knowledge profiles per entity.
8
+ When a new fact mentions an entity, the entity's profile is updated.
9
+
10
+ V1 had this module but NEVER CALLED it. Now wired into the encoding pipeline.
11
+
12
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import logging
19
+ from datetime import UTC, datetime
20
+
21
+ from superlocalmemory.storage.models import AtomicFact, EntityProfile
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class ObservationBuilder:
27
+ """Build and maintain entity knowledge profiles.
28
+
29
+ Each canonical entity gets a running profile that accumulates
30
+ all facts known about it. Used for:
31
+ - Entity-centric retrieval (return profile as context)
32
+ - Consolidation (detect when new info conflicts with profile)
33
+ - Answer generation (entity profiles provide rich context)
34
+ """
35
+
36
+ def __init__(self, db) -> None:
37
+ self._db = db
38
+
39
+ def update_profile(
40
+ self,
41
+ entity_id: str,
42
+ new_fact: AtomicFact,
43
+ profile_id: str,
44
+ ) -> EntityProfile:
45
+ """Update (or create) entity profile with new fact.
46
+
47
+ Appends fact to profile's fact list and regenerates summary.
48
+ """
49
+ existing = self._get_profile(entity_id, profile_id)
50
+
51
+ if existing is not None:
52
+ fact_ids = existing.fact_ids
53
+ if new_fact.fact_id not in fact_ids:
54
+ fact_ids = [*fact_ids, new_fact.fact_id]
55
+ summary = self._build_summary(entity_id, fact_ids, profile_id)
56
+ updated = EntityProfile(
57
+ profile_entry_id=existing.profile_entry_id,
58
+ entity_id=entity_id,
59
+ profile_id=profile_id,
60
+ knowledge_summary=summary,
61
+ fact_ids=fact_ids,
62
+ last_updated=datetime.now(UTC).isoformat(),
63
+ )
64
+ else:
65
+ summary = self._build_summary(entity_id, [new_fact.fact_id], profile_id)
66
+ updated = EntityProfile(
67
+ entity_id=entity_id,
68
+ profile_id=profile_id,
69
+ knowledge_summary=summary,
70
+ fact_ids=[new_fact.fact_id],
71
+ last_updated=datetime.now(UTC).isoformat(),
72
+ )
73
+
74
+ self._save_profile(updated)
75
+ return updated
76
+
77
+ def get_profile(self, entity_id: str, profile_id: str) -> EntityProfile | None:
78
+ """Get the current knowledge profile for an entity."""
79
+ return self._get_profile(entity_id, profile_id)
80
+
81
+ def build_all_profiles(self, profile_id: str) -> list[EntityProfile]:
82
+ """Rebuild all entity profiles from scratch. Use after migration."""
83
+ rows = self._db.execute(
84
+ "SELECT DISTINCT entity_id FROM canonical_entities WHERE profile_id = ?",
85
+ (profile_id,),
86
+ )
87
+ profiles = []
88
+ for row in rows:
89
+ eid = dict(row)["entity_id"]
90
+ facts = self._db.get_facts_by_entity(eid, profile_id)
91
+ if facts:
92
+ fact_ids = [f.fact_id for f in facts]
93
+ summary = self._build_summary(eid, fact_ids, profile_id)
94
+ ep = EntityProfile(
95
+ entity_id=eid,
96
+ profile_id=profile_id,
97
+ knowledge_summary=summary,
98
+ fact_ids=fact_ids,
99
+ last_updated=datetime.now(UTC).isoformat(),
100
+ )
101
+ self._save_profile(ep)
102
+ profiles.append(ep)
103
+ return profiles
104
+
105
+ # -- Internal ----------------------------------------------------------
106
+
107
+ def _get_profile(self, entity_id: str, profile_id: str) -> EntityProfile | None:
108
+ """Load entity profile from DB."""
109
+ rows = self._db.execute(
110
+ "SELECT * FROM entity_profiles WHERE entity_id = ? AND profile_id = ?",
111
+ (entity_id, profile_id),
112
+ )
113
+ if not rows:
114
+ return None
115
+ d = dict(rows[0])
116
+ return EntityProfile(
117
+ profile_entry_id=d["profile_entry_id"],
118
+ entity_id=d["entity_id"],
119
+ profile_id=d["profile_id"],
120
+ knowledge_summary=d.get("knowledge_summary", ""),
121
+ fact_ids=json.loads(d.get("fact_ids_json", "[]")),
122
+ last_updated=d.get("last_updated", ""),
123
+ )
124
+
125
+ def _save_profile(self, profile: EntityProfile) -> None:
126
+ """Upsert entity profile to DB."""
127
+ self._db.execute(
128
+ """INSERT OR REPLACE INTO entity_profiles
129
+ (profile_entry_id, entity_id, profile_id,
130
+ knowledge_summary, fact_ids_json, last_updated)
131
+ VALUES (?, ?, ?, ?, ?, ?)""",
132
+ (
133
+ profile.profile_entry_id,
134
+ profile.entity_id,
135
+ profile.profile_id,
136
+ profile.knowledge_summary,
137
+ json.dumps(profile.fact_ids),
138
+ profile.last_updated,
139
+ ),
140
+ )
141
+
142
+ def _build_summary(
143
+ self, entity_id: str, fact_ids: list[str], profile_id: str
144
+ ) -> str:
145
+ """Build a knowledge summary from all facts about an entity.
146
+
147
+ Simple concatenation for now. Mode B/C could use LLM summarization.
148
+ """
149
+ facts = []
150
+ for fid in fact_ids[-20:]: # Last 20 facts to keep summary manageable
151
+ rows = self._db.execute(
152
+ "SELECT content FROM atomic_facts WHERE fact_id = ? AND profile_id = ?",
153
+ (fid, profile_id),
154
+ )
155
+ if rows:
156
+ facts.append(dict(rows[0])["content"])
157
+
158
+ if not facts:
159
+ return ""
160
+ return " | ".join(facts)