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,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