superlocalmemory 2.8.6 → 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 (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 +1 -1
  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,398 @@
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
+ """Riemannian Langevin dynamics with persistence for memory lifecycle.
6
+
7
+ Evolves memory positions via a discretised Langevin SDE driven by an
8
+ access-aware potential function. Frequently accessed memories are
9
+ confined near the origin (ACTIVE, high retrieval weight) by a strong
10
+ potential, while neglected memories diffuse toward the boundary
11
+ (ARCHIVED, weight approaches 0) as their confinement weakens.
12
+
13
+ Core SDE (Girolami & Calderhead 2011):
14
+
15
+ xi_{t+1} = xi_t - g^{-1}(xi_t) * grad U(xi_t) * dt
16
+ + sqrt(2 * T * dt) * g^{-1/2}(xi_t) * eta
17
+
18
+ Potential function:
19
+
20
+ U(xi) = alpha * ||xi||^2
21
+ - beta * log(access_count + 1)
22
+ + gamma * age_days
23
+ - delta * importance
24
+
25
+ - Accessed memories: beta term lowers potential -> drift to origin
26
+ - Old / unused: gamma term raises potential -> drift to boundary
27
+ - Important memories: delta term lowers potential -> retain near origin
28
+
29
+ V1 bugs fixed in Innovation Wave 4:
30
+ 1. Positions were computed per-recall then DISCARDED. Now ``step()``
31
+ and ``batch_step()`` return new positions for the caller to persist.
32
+ 2. Weight range was [0.7, 1.0] --- too narrow to change rankings.
33
+ Now [0.0, 1.0] so archived memories can be effectively suppressed.
34
+
35
+ References:
36
+ Girolami M & Calderhead B (2011). Riemann manifold Langevin and
37
+ Hamiltonian Monte Carlo methods. JRSS-B, 73(2), 123--214.
38
+ Roberts G O & Tweedie R L (1996). Exponential convergence of Langevin
39
+ distributions. Bernoulli, 2(4), 341--363.
40
+ Xifara T, Sherlock C, Livingstone S, Byrne S & Girolami M (2014).
41
+ Langevin diffusions and the MALA algorithm. Statistics &
42
+ Probability Letters, 91, 59--67.
43
+
44
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
45
+ License: MIT
46
+ """
47
+
48
+ from __future__ import annotations
49
+
50
+ import math
51
+ from typing import Any
52
+
53
+ import numpy as np
54
+
55
+ from superlocalmemory.storage.models import MemoryLifecycle
56
+
57
+ # ---------------------------------------------------------------------------
58
+ # Lifecycle zone boundaries (radius thresholds on the unit ball)
59
+ # ---------------------------------------------------------------------------
60
+
61
+ _RADIUS_ACTIVE: float = 0.3 # [0, 0.3) -> ACTIVE
62
+ _RADIUS_WARM: float = 0.55 # [0.3, 0.55) -> WARM
63
+ _RADIUS_COLD: float = 0.8 # [0.55, 0.8) -> COLD
64
+ # [0.8, 1.0) -> ARCHIVED
65
+
66
+ # Potential coefficients (defaults)
67
+ # Alpha must be > 0.5*T*(d-2)/lam_inv ≈ 1.8 at T=0.3 for confinement
68
+ # to counter the Riemannian curvature correction near the origin.
69
+ _ALPHA: float = 3.0 # Base confinement (strong enough to counter correction)
70
+ _BETA: float = 0.8 # Access STRENGTHENS confinement (keeps active facts near origin)
71
+ _GAMMA: float = 0.005 # Age WEAKENS confinement (old facts drift to boundary)
72
+ _DELTA: float = 0.5 # Importance STRENGTHENS confinement
73
+
74
+ # Safety
75
+ _MAX_NORM: float = 0.99 # Clamp radius < 1 (open ball)
76
+ _EPS: float = 1e-8
77
+
78
+
79
+ # ---------------------------------------------------------------------------
80
+ # Public API
81
+ # ---------------------------------------------------------------------------
82
+
83
+ class LangevinDynamics:
84
+ """Riemannian Langevin dynamics with persistence for memory lifecycle.
85
+
86
+ Positions live on the open unit ball B^d. The potential function
87
+ encodes access frequency, age, and importance as competing forces.
88
+ The lifecycle state (ACTIVE / WARM / COLD / ARCHIVED) is determined
89
+ purely by radial position.
90
+
91
+ Attributes:
92
+ dt: Time-step size for Euler-Maruyama integration.
93
+ temperature: Boltzmann temperature controlling diffusion spread.
94
+ weight_range: (min_weight, max_weight) for lifecycle weighting.
95
+ dim: Dimensionality of the position space.
96
+ """
97
+
98
+ __slots__ = (
99
+ "dt", "temperature", "weight_range", "dim",
100
+ "_alpha", "_beta", "_gamma", "_delta",
101
+ )
102
+
103
+ def __init__(
104
+ self,
105
+ dt: float = 0.01,
106
+ temperature: float = 1.0,
107
+ weight_range: tuple[float, float] = (0.0, 1.0),
108
+ dim: int = 8,
109
+ ) -> None:
110
+ if dt <= 0:
111
+ raise ValueError(f"dt must be positive, got {dt}")
112
+ if temperature <= 0:
113
+ raise ValueError(f"temperature must be positive, got {temperature}")
114
+ if weight_range[0] > weight_range[1]:
115
+ raise ValueError(
116
+ f"weight_range min > max: {weight_range[0]} > {weight_range[1]}"
117
+ )
118
+
119
+ self.dt = dt
120
+ self.temperature = temperature
121
+ self.weight_range = weight_range
122
+ self.dim = dim
123
+
124
+ # Potential coefficients
125
+ self._alpha = _ALPHA
126
+ self._beta = _BETA
127
+ self._gamma = _GAMMA
128
+ self._delta = _DELTA
129
+
130
+ # ------------------------------------------------------------------
131
+ # Single-fact step
132
+ # ------------------------------------------------------------------
133
+
134
+ def step(
135
+ self,
136
+ position: list[float],
137
+ access_count: int,
138
+ age_days: float,
139
+ importance: float,
140
+ seed: int | None = None,
141
+ ) -> tuple[list[float], float]:
142
+ """One Euler-Maruyama step of the Langevin SDE.
143
+
144
+ Computes the gradient of the potential at the current position,
145
+ applies drift + stochastic diffusion, and returns the new position
146
+ plus the resulting lifecycle weight.
147
+
148
+ The caller is responsible for PERSISTING the returned position
149
+ back to the database. This is the key V1 fix: positions must
150
+ evolve across recalls, not be recomputed from scratch each time.
151
+
152
+ Args:
153
+ position: Current position on the unit ball, length ``dim``.
154
+ access_count: Total number of times this fact has been accessed.
155
+ age_days: Days since the fact was first stored.
156
+ importance: Fact importance score in [0, 1].
157
+ seed: Optional RNG seed for reproducible diffusion.
158
+
159
+ Returns:
160
+ (new_position, lifecycle_weight) where new_position is a list
161
+ of floats inside the unit ball and lifecycle_weight is in
162
+ [weight_range[0], weight_range[1]].
163
+ """
164
+ xi = np.asarray(position, dtype=np.float64)
165
+ if xi.shape[0] != self.dim:
166
+ # Graceful resize: pad or truncate
167
+ xi = _resize_position(xi, self.dim)
168
+
169
+ # --- Gradient of potential ---
170
+ grad = self._potential_gradient(xi, access_count, age_days, importance)
171
+
172
+ # --- Conformal factor on Poincare ball: lambda = 2 / (1 - ||x||^2) ---
173
+ norm_sq = float(np.dot(xi, xi))
174
+ norm_sq = min(norm_sq, _MAX_NORM ** 2)
175
+ lam = 2.0 / (1.0 - norm_sq + _EPS)
176
+ lam_inv = 1.0 / lam
177
+
178
+ # --- Drift: -lambda^{-2} * grad_U * dt (Eq. 5 term 1) ---
179
+ drift = -(lam_inv ** 2) * grad * self.dt
180
+
181
+ # --- Curvature correction: 0.5 * T * (d-2) * lambda^{-1} * xi * dt (Eq. 5 term 3) ---
182
+ correction = 0.5 * self.temperature * (self.dim - 2) * lam_inv * xi * self.dt
183
+
184
+ # --- Diffusion: sqrt(2T * dt) * lambda^{-1} * noise (Eq. 5 term 2) ---
185
+ rng = np.random.default_rng(seed)
186
+ noise = rng.standard_normal(self.dim)
187
+ diffusion = math.sqrt(2.0 * self.temperature * self.dt) * lam_inv * noise
188
+
189
+ # --- Full Euler-Maruyama update (Girolami & Calderhead 2011) ---
190
+ new_xi = xi + drift + correction + diffusion
191
+
192
+ # --- Project back into the open ball ---
193
+ new_xi = _project_to_ball(new_xi)
194
+
195
+ weight = self.compute_lifecycle_weight(new_xi.tolist())
196
+ return new_xi.tolist(), weight
197
+
198
+ # ------------------------------------------------------------------
199
+ # Lifecycle weight from position
200
+ # ------------------------------------------------------------------
201
+
202
+ def compute_lifecycle_weight(self, position: list[float]) -> float:
203
+ """Compute retrieval weight from radial position.
204
+
205
+ Weight decreases linearly from max to min as radius moves from
206
+ origin to boundary. This ensures archived memories (near boundary)
207
+ are strongly suppressed in retrieval rankings.
208
+
209
+ Args:
210
+ position: Current position, length ``dim``.
211
+
212
+ Returns:
213
+ Weight in [weight_range[0], weight_range[1]].
214
+ """
215
+ xi = np.asarray(position, dtype=np.float64)
216
+ radius = float(np.linalg.norm(xi))
217
+ radius = min(radius, _MAX_NORM)
218
+
219
+ # Linear interpolation: radius 0 -> max weight, radius 1 -> min weight
220
+ lo, hi = self.weight_range
221
+ weight = hi - (hi - lo) * (radius / _MAX_NORM)
222
+ return max(lo, min(hi, weight))
223
+
224
+ # ------------------------------------------------------------------
225
+ # Lifecycle state classification
226
+ # ------------------------------------------------------------------
227
+
228
+ def get_lifecycle_state(self, weight: float) -> MemoryLifecycle:
229
+ """Classify lifecycle state from weight.
230
+
231
+ Uses the weight (derived from radius) rather than raw radius,
232
+ so the caller doesn't need to know zone boundaries.
233
+
234
+ Args:
235
+ weight: Lifecycle weight in [0, 1].
236
+
237
+ Returns:
238
+ MemoryLifecycle enum value.
239
+ """
240
+ # Map weight back to approximate radius for zone classification
241
+ lo, hi = self.weight_range
242
+ span = hi - lo
243
+ if span < _EPS:
244
+ return MemoryLifecycle.ACTIVE
245
+
246
+ # radius_approx: weight=hi -> radius=0, weight=lo -> radius=MAX_NORM
247
+ radius_approx = (hi - weight) / span * _MAX_NORM
248
+
249
+ if radius_approx < _RADIUS_ACTIVE:
250
+ return MemoryLifecycle.ACTIVE
251
+ if radius_approx < _RADIUS_WARM:
252
+ return MemoryLifecycle.WARM
253
+ if radius_approx < _RADIUS_COLD:
254
+ return MemoryLifecycle.COLD
255
+ return MemoryLifecycle.ARCHIVED
256
+
257
+ # ------------------------------------------------------------------
258
+ # Batch step (background maintenance)
259
+ # ------------------------------------------------------------------
260
+
261
+ def batch_step(
262
+ self,
263
+ facts: list[dict[str, Any]],
264
+ seed: int | None = None,
265
+ ) -> list[dict[str, Any]]:
266
+ """Batch Langevin step for background maintenance.
267
+
268
+ Evolves all fact positions in a single pass. Designed to be
269
+ called periodically (e.g. every N hours) to let the memory store
270
+ self-organise over time.
271
+
272
+ Each input dict must contain:
273
+ - ``fact_id``: str
274
+ - ``position``: list[float]
275
+ - ``access_count``: int
276
+ - ``age_days``: float
277
+ - ``importance``: float
278
+
279
+ Args:
280
+ facts: List of fact dicts with position and metadata.
281
+ seed: Optional base seed (incremented per fact for variety).
282
+
283
+ Returns:
284
+ List of dicts with ``fact_id``, ``position`` (updated),
285
+ ``weight``, and ``lifecycle`` fields.
286
+ """
287
+ results: list[dict[str, Any]] = []
288
+ base_seed = seed
289
+
290
+ for i, fact in enumerate(facts):
291
+ fact_seed = (base_seed + i) if base_seed is not None else None
292
+
293
+ new_pos, weight = self.step(
294
+ position=fact["position"],
295
+ access_count=fact["access_count"],
296
+ age_days=fact["age_days"],
297
+ importance=fact["importance"],
298
+ seed=fact_seed,
299
+ )
300
+
301
+ results.append({
302
+ "fact_id": fact["fact_id"],
303
+ "position": new_pos,
304
+ "weight": weight,
305
+ "lifecycle": self.get_lifecycle_state(weight).value,
306
+ })
307
+
308
+ return results
309
+
310
+ # ------------------------------------------------------------------
311
+ # Potential gradient (private)
312
+ # ------------------------------------------------------------------
313
+
314
+ def _potential_gradient(
315
+ self,
316
+ xi: np.ndarray,
317
+ access_count: int,
318
+ age_days: float,
319
+ importance: float,
320
+ ) -> np.ndarray:
321
+ """Metadata-modulated confinement gradient .
322
+
323
+ Potential:
324
+ U(xi) = effective_alpha * ||xi||^2
325
+
326
+ Where effective_alpha is modulated by metadata:
327
+ effective_alpha = alpha
328
+ + beta * log(access+1)/10 (frequent access → stronger confinement → ACTIVE)
329
+ - gamma * min(age, 365)/365 (aging → weaker confinement → drifts to ARCHIVED)
330
+ + delta * importance (important → stronger confinement → stays ACTIVE)
331
+
332
+ Gradient:
333
+ grad U = 2 * effective_alpha * xi
334
+
335
+ This ensures metadata drives correct lifecycle dynamics:
336
+ - Frequently accessed facts stay near origin (ACTIVE, high weight)
337
+ - Old unused facts drift toward boundary (ARCHIVED, low weight)
338
+ - Important facts resist archival (persistent)
339
+
340
+ Args:
341
+ xi: Position array.
342
+ access_count: Total access count.
343
+ age_days: Days since creation.
344
+ importance: Importance score [0, 1].
345
+
346
+ Returns:
347
+ Gradient vector, same shape as xi.
348
+ """
349
+ import math as _math
350
+ effective_alpha = (
351
+ self._alpha
352
+ + self._beta * _math.log(access_count + 1) / 10.0
353
+ - self._gamma * min(age_days, 365.0) / 365.0
354
+ + self._delta * importance
355
+ )
356
+ effective_alpha = max(0.1, effective_alpha)
357
+ return 2.0 * effective_alpha * xi
358
+
359
+
360
+ # ---------------------------------------------------------------------------
361
+ # Helpers (module-private)
362
+ # ---------------------------------------------------------------------------
363
+
364
+ def _project_to_ball(xi: np.ndarray) -> np.ndarray:
365
+ """Project point back into the open unit ball.
366
+
367
+ If ||xi|| >= MAX_NORM, rescale to sit at MAX_NORM.
368
+
369
+ Args:
370
+ xi: Position array.
371
+
372
+ Returns:
373
+ Projected position with ||result|| < 1.
374
+ """
375
+ norm = float(np.linalg.norm(xi))
376
+ if norm >= _MAX_NORM:
377
+ return xi * (_MAX_NORM / (norm + _EPS))
378
+ return xi
379
+
380
+
381
+ def _resize_position(xi: np.ndarray, target_dim: int) -> np.ndarray:
382
+ """Resize a position vector to target dimensionality.
383
+
384
+ Pads with zeros or truncates as needed.
385
+
386
+ Args:
387
+ xi: Source position.
388
+ target_dim: Desired dimensionality.
389
+
390
+ Returns:
391
+ Resized position array.
392
+ """
393
+ current = xi.shape[0]
394
+ if current == target_dim:
395
+ return xi
396
+ if current < target_dim:
397
+ return np.pad(xi, (0, target_dim - current), constant_values=0.0)
398
+ return xi[:target_dim]
@@ -0,0 +1,257 @@
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
+ """Sheaf cohomology for contradiction detection at ENCODING time.
6
+
7
+ V1 applied sheaf at RECALL (HARMFUL — penalized diverse multi-hop results).
8
+ V2 runs at ENCODING ONLY, checking new facts against graph-connected facts.
9
+
10
+ Coboundary: delta_0 f(e) = R_b emb_b - R_a emb_a
11
+ Restriction maps by edge type: ENTITY=I, TEMPORAL=s*I (s<1), SEMANTIC=I.
12
+ Severity = ||delta_0 f(e)|| / (||R_a emb_a|| + ||R_b emb_b||).
13
+
14
+ Refs: Curry 2014 (arXiv:1303.3255), Hansen & Ghrist 2019, Robinson 2020.
15
+
16
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
17
+ License: MIT
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import logging
22
+ from dataclasses import dataclass
23
+ from typing import TYPE_CHECKING
24
+
25
+ import numpy as np
26
+
27
+ if TYPE_CHECKING:
28
+ from superlocalmemory.storage.database import DatabaseManager
29
+ from superlocalmemory.storage.models import AtomicFact
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+ _EPS = 1e-12
34
+
35
+ # Temporal tolerance: fraction of coboundary norm below which temporal
36
+ # edges are NOT flagged as contradictions (legitimate state changes).
37
+ TEMPORAL_TOLERANCE: float = 0.15
38
+
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # Result type
42
+ # ---------------------------------------------------------------------------
43
+
44
+ @dataclass(frozen=True)
45
+ class ContradictionResult:
46
+ """Detected contradiction between two facts."""
47
+
48
+ fact_id_a: str # New fact being stored
49
+ fact_id_b: str # Existing fact it conflicts with
50
+ severity: float # Normalized disagreement [0.0, 1.0]
51
+ edge_type: str # Graph edge type (entity/temporal/semantic/causal)
52
+ description: str
53
+
54
+
55
+ # ---------------------------------------------------------------------------
56
+ # Restriction maps — non-trivial (V1 used identity everywhere)
57
+ # ---------------------------------------------------------------------------
58
+
59
+ def _restriction_for_edge_type(
60
+ edge_type: str,
61
+ dim: int,
62
+ emb_a: np.ndarray | None = None,
63
+ emb_b: np.ndarray | None = None,
64
+ ) -> np.ndarray:
65
+ """Edge-type-specific restriction map.
66
+
67
+ Entity edges use non-trivial maps that AMPLIFY the disagreement
68
+ along the axis of maximum difference between embeddings:
69
+ R = 0.7*I + 0.3 * outer(diff, diff) / ||diff||^2
70
+
71
+ This projects contradictions onto the most discriminative subspace,
72
+ making the coboundary norm more sensitive to real disagreements.
73
+
74
+ Temporal edges use identity restriction (scalar multiples cancel
75
+ in the normalized coboundary). Temporal tolerance is applied at
76
+ the caller level via TEMPORAL_TOLERANCE threshold.
77
+ """
78
+ if edge_type == "entity" and emb_a is not None and emb_b is not None:
79
+ diff = emb_a - emb_b
80
+ norm_sq = max(float(np.dot(diff, diff)), 1e-12)
81
+ return 0.7 * np.eye(dim) + 0.3 * np.outer(diff, diff) / norm_sq
82
+ return np.eye(dim)
83
+
84
+
85
+ def edge_residual(
86
+ emb_a: np.ndarray, emb_b: np.ndarray,
87
+ R_a: np.ndarray, R_b: np.ndarray,
88
+ ) -> np.ndarray:
89
+ """Coboundary residual: delta_0 f(e) = R_b emb_b - R_a emb_a."""
90
+ return R_b @ emb_b - R_a @ emb_a
91
+
92
+
93
+ def coboundary_norm(
94
+ emb_a: np.ndarray, emb_b: np.ndarray,
95
+ R_a: np.ndarray, R_b: np.ndarray,
96
+ ) -> float:
97
+ """Normalized coboundary: ||delta|| / (||R_a emb_a|| + ||R_b emb_b||).
98
+
99
+ Returns [0, ~2]. Near 0 = agree; near 1+ = strong disagreement.
100
+ """
101
+ residual = edge_residual(emb_a, emb_b, R_a, R_b)
102
+ res_norm = float(np.linalg.norm(residual))
103
+ denom = float(np.linalg.norm(R_a @ emb_a) + np.linalg.norm(R_b @ emb_b) + _EPS)
104
+ return res_norm / denom
105
+
106
+
107
+ class SheafConsistencyChecker:
108
+ """Detect contradictions at ENCODING time (not retrieval).
109
+
110
+ V1 differences: uses real graph edges (not all-pairs), non-trivial
111
+ restriction maps, and returns results for the pipeline to act on.
112
+ """
113
+
114
+ def __init__(
115
+ self,
116
+ db: DatabaseManager,
117
+ contradiction_threshold: float = 0.7,
118
+ ) -> None:
119
+ self._db = db
120
+ self._threshold = max(0.0, min(2.0, contradiction_threshold))
121
+
122
+ # -- Public API ---------------------------------------------------------
123
+
124
+ def check_consistency(
125
+ self,
126
+ new_fact: AtomicFact,
127
+ profile_id: str,
128
+ ) -> list[ContradictionResult]:
129
+ """Check new fact against graph-connected existing facts."""
130
+ if new_fact.embedding is None:
131
+ return []
132
+ if not new_fact.canonical_entities:
133
+ return []
134
+
135
+ emb_a = np.asarray(new_fact.embedding, dtype=np.float64)
136
+ dim = emb_a.shape[0]
137
+ if dim == 0:
138
+ return []
139
+
140
+ contradictions: list[ContradictionResult] = []
141
+ checked_pairs: set[str] = set()
142
+
143
+ # Get all graph edges touching this fact
144
+ edges = self._db.get_edges_for_node(new_fact.fact_id, profile_id)
145
+
146
+ for edge in edges:
147
+ # Determine the OTHER fact in this edge
148
+ other_id = (
149
+ edge.target_id
150
+ if edge.source_id == new_fact.fact_id
151
+ else edge.source_id
152
+ )
153
+
154
+ # Skip already-checked pairs and skip contradiction/supersedes edges
155
+ if other_id in checked_pairs:
156
+ continue
157
+ if edge.edge_type.value in ("contradiction", "supersedes"):
158
+ continue
159
+ checked_pairs.add(other_id)
160
+
161
+ # Look up the other fact's embedding
162
+ other_emb = self._get_fact_embedding(other_id)
163
+ if other_emb is None or other_emb.shape[0] != dim:
164
+ continue
165
+
166
+ # Compute coboundary with edge-type-specific restriction map
167
+ edge_type_str = edge.edge_type.value
168
+ R = _restriction_for_edge_type(edge_type_str, dim, emb_a, other_emb)
169
+ severity = coboundary_norm(emb_a, other_emb, R, R)
170
+
171
+ if severity > self._threshold:
172
+ contradictions.append(ContradictionResult(
173
+ fact_id_a=new_fact.fact_id,
174
+ fact_id_b=other_id,
175
+ severity=min(severity, 1.0),
176
+ edge_type=edge_type_str,
177
+ description=(
178
+ f"Sheaf coboundary {severity:.3f} > {self._threshold:.2f} "
179
+ f"along {edge_type_str} edge"
180
+ ),
181
+ ))
182
+
183
+ if contradictions:
184
+ logger.info(
185
+ "Sheaf: %d contradiction(s) for fact %s",
186
+ len(contradictions), new_fact.fact_id,
187
+ )
188
+ return contradictions
189
+
190
+ def detect_contradictions_batch(
191
+ self,
192
+ facts: list[AtomicFact],
193
+ profile_id: str,
194
+ ) -> list[ContradictionResult]:
195
+ """Pairwise check within entity groups (for batch imports)."""
196
+ # Group facts by canonical entity
197
+ entity_groups: dict[str, list[AtomicFact]] = {}
198
+ for fact in facts:
199
+ if fact.embedding is None:
200
+ continue
201
+ for eid in fact.canonical_entities:
202
+ entity_groups.setdefault(eid, []).append(fact)
203
+
204
+ contradictions: list[ContradictionResult] = []
205
+ checked_pairs: set[tuple[str, str]] = set()
206
+
207
+ for _entity_id, group in entity_groups.items():
208
+ for i in range(len(group)):
209
+ for j in range(i + 1, len(group)):
210
+ fa, fb = group[i], group[j]
211
+ pair = (min(fa.fact_id, fb.fact_id), max(fa.fact_id, fb.fact_id))
212
+ if pair in checked_pairs:
213
+ continue
214
+ checked_pairs.add(pair)
215
+
216
+ emb_a = np.asarray(fa.embedding, dtype=np.float64)
217
+ emb_b = np.asarray(fb.embedding, dtype=np.float64)
218
+ if emb_a.shape[0] != emb_b.shape[0] or emb_a.shape[0] == 0:
219
+ continue
220
+
221
+ dim = emb_a.shape[0]
222
+ R = np.eye(dim)
223
+ severity = coboundary_norm(emb_a, emb_b, R, R)
224
+
225
+ if severity > self._threshold:
226
+ contradictions.append(ContradictionResult(
227
+ fact_id_a=fa.fact_id,
228
+ fact_id_b=fb.fact_id,
229
+ severity=min(severity, 1.0),
230
+ edge_type="entity",
231
+ description=(
232
+ f"Batch sheaf coboundary {severity:.3f} > "
233
+ f"{self._threshold:.2f} (shared entity group)"
234
+ ),
235
+ ))
236
+
237
+ return contradictions
238
+
239
+ # -- Internal -----------------------------------------------------------
240
+
241
+ def _get_fact_embedding(self, fact_id: str) -> np.ndarray | None:
242
+ """Load a fact's embedding from the database."""
243
+ import json
244
+ rows = self._db.execute(
245
+ "SELECT embedding FROM atomic_facts WHERE fact_id = ?",
246
+ (fact_id,),
247
+ )
248
+ if not rows:
249
+ return None
250
+ raw = dict(rows[0]).get("embedding")
251
+ if raw is None or raw == "":
252
+ return None
253
+ try:
254
+ data = json.loads(raw) if isinstance(raw, str) else raw
255
+ return np.asarray(data, dtype=np.float64)
256
+ except (json.JSONDecodeError, TypeError, ValueError):
257
+ return None
File without changes