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
@@ -1,989 +0,0 @@
1
- #!/usr/bin/env python3
2
- # SPDX-License-Identifier: MIT
3
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
4
- """
5
- Progressive Summarization Compression for SuperLocalMemory
6
- Tier-based compression system to maintain 100+ memories efficiently.
7
-
8
- Tier Strategy:
9
- - Tier 1 (0-30 days): Full content (no compression)
10
- - Tier 2 (30-90 days): Summary + key excerpts (~80% reduction)
11
- - Tier 3 (90+ days): Bullet points only (~96% reduction)
12
- - Cold Storage (1+ year): Gzipped JSON archives (~98% reduction)
13
-
14
- No external LLM calls - all compression is extractive using local algorithms.
15
- """
16
-
17
- import sqlite3
18
- import json
19
- import gzip
20
- import re
21
- from datetime import datetime, timedelta
22
- from pathlib import Path
23
- from typing import List, Dict, Optional, Tuple, Any
24
- import hashlib
25
-
26
-
27
- MEMORY_DIR = Path.home() / ".claude-memory"
28
- DB_PATH = MEMORY_DIR / "memory.db"
29
- CONFIG_PATH = MEMORY_DIR / "config.json"
30
- COLD_STORAGE_PATH = MEMORY_DIR / "cold-storage"
31
- LOGS_PATH = MEMORY_DIR / "logs"
32
-
33
-
34
- class CompressionConfig:
35
- """Configuration for compression behavior."""
36
-
37
- def __init__(self):
38
- self.config = self._load_config()
39
- self.compression_settings = self.config.get('compression', {})
40
-
41
- def _load_config(self) -> Dict[str, Any]:
42
- """Load configuration from config.json."""
43
- if CONFIG_PATH.exists():
44
- with open(CONFIG_PATH, 'r') as f:
45
- return json.load(f)
46
- return {}
47
-
48
- def save(self) -> None:
49
- """Save configuration back to config.json."""
50
- with open(CONFIG_PATH, 'w') as f:
51
- json.dump(self.config, f, indent=2)
52
-
53
- @property
54
- def enabled(self) -> bool:
55
- return self.compression_settings.get('enabled', True)
56
-
57
- @property
58
- def tier2_threshold_days(self) -> int:
59
- return self.compression_settings.get('tier2_threshold_days', 30)
60
-
61
- @property
62
- def tier3_threshold_days(self) -> int:
63
- return self.compression_settings.get('tier3_threshold_days', 90)
64
-
65
- @property
66
- def cold_storage_threshold_days(self) -> int:
67
- return self.compression_settings.get('cold_storage_threshold_days', 365)
68
-
69
- @property
70
- def preserve_high_importance(self) -> bool:
71
- return self.compression_settings.get('preserve_high_importance', True)
72
-
73
- @property
74
- def preserve_recently_accessed(self) -> bool:
75
- return self.compression_settings.get('preserve_recently_accessed', True)
76
-
77
- def initialize_defaults(self) -> None:
78
- """Initialize compression settings in config if not present."""
79
- if 'compression' not in self.config:
80
- self.config['compression'] = {
81
- 'enabled': True,
82
- 'tier2_threshold_days': 30,
83
- 'tier3_threshold_days': 90,
84
- 'cold_storage_threshold_days': 365,
85
- 'preserve_high_importance': True,
86
- 'preserve_recently_accessed': True
87
- }
88
- self.save()
89
-
90
-
91
- class TierClassifier:
92
- """Classify memories into compression tiers based on age and access patterns."""
93
-
94
- def __init__(self, db_path: Path = DB_PATH):
95
- self.db_path = db_path
96
- self.config = CompressionConfig()
97
- self._ensure_schema()
98
-
99
- def _ensure_schema(self):
100
- """Add tier and access tracking columns if not present."""
101
- conn = sqlite3.connect(self.db_path)
102
- cursor = conn.cursor()
103
-
104
- # Check if tier column exists
105
- cursor.execute("PRAGMA table_info(memories)")
106
- columns = [row[1] for row in cursor.fetchall()]
107
-
108
- if 'tier' not in columns:
109
- cursor.execute('ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 1')
110
- cursor.execute('CREATE INDEX IF NOT EXISTS idx_tier ON memories(tier)')
111
-
112
- if 'last_accessed' not in columns:
113
- cursor.execute('ALTER TABLE memories ADD COLUMN last_accessed TIMESTAMP')
114
-
115
- if 'access_count' not in columns:
116
- cursor.execute('ALTER TABLE memories ADD COLUMN access_count INTEGER DEFAULT 0')
117
-
118
- # Create memory_archive table if not exists
119
- cursor.execute('''
120
- CREATE TABLE IF NOT EXISTS memory_archive (
121
- id INTEGER PRIMARY KEY AUTOINCREMENT,
122
- memory_id INTEGER UNIQUE NOT NULL,
123
- full_content TEXT NOT NULL,
124
- archived_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
125
- FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE
126
- )
127
- ''')
128
- cursor.execute('CREATE INDEX IF NOT EXISTS idx_archive_memory ON memory_archive(memory_id)')
129
-
130
- conn.commit()
131
- conn.close()
132
-
133
- def classify_memories(self) -> List[Tuple[int, int]]:
134
- """
135
- Classify all memories into tiers based on age and access.
136
-
137
- Returns:
138
- List of (tier, memory_id) tuples
139
- """
140
- if not self.config.enabled:
141
- return []
142
-
143
- now = datetime.now()
144
- conn = sqlite3.connect(self.db_path)
145
- cursor = conn.cursor()
146
-
147
- # Get all memories with access tracking
148
- cursor.execute('''
149
- SELECT id, created_at, last_accessed, access_count, importance, tier
150
- FROM memories
151
- ''')
152
- memories = cursor.fetchall()
153
-
154
- tier_updates = []
155
-
156
- for memory_id, created_at, last_accessed, access_count, importance, current_tier in memories:
157
- created = datetime.fromisoformat(created_at)
158
- age_days = (now - created).days
159
-
160
- # Override: High-importance memories stay in Tier 1
161
- if self.config.preserve_high_importance and importance and importance >= 8:
162
- tier = 1
163
- # Recently accessed stays in Tier 1
164
- elif self.config.preserve_recently_accessed and last_accessed:
165
- last_access = datetime.fromisoformat(last_accessed)
166
- if (now - last_access).days < 7:
167
- tier = 1
168
- else:
169
- tier = self._classify_by_age(age_days)
170
- # Age-based classification
171
- else:
172
- tier = self._classify_by_age(age_days)
173
-
174
- # Only update if tier changed
175
- if tier != current_tier:
176
- tier_updates.append((tier, memory_id))
177
-
178
- # Update tier field
179
- if tier_updates:
180
- cursor.executemany('''
181
- UPDATE memories SET tier = ? WHERE id = ?
182
- ''', tier_updates)
183
- conn.commit()
184
-
185
- conn.close()
186
- return tier_updates
187
-
188
- def _classify_by_age(self, age_days: int) -> int:
189
- """Classify memory tier based on age."""
190
- if age_days < self.config.tier2_threshold_days:
191
- return 1 # Recent
192
- elif age_days < self.config.tier3_threshold_days:
193
- return 2 # Active
194
- else:
195
- return 3 # Archived
196
-
197
- def get_tier_stats(self) -> Dict[str, int]:
198
- """Get count of memories in each tier."""
199
- conn = sqlite3.connect(self.db_path)
200
- cursor = conn.cursor()
201
-
202
- cursor.execute('''
203
- SELECT tier, COUNT(*) FROM memories GROUP BY tier
204
- ''')
205
- stats = dict(cursor.fetchall())
206
- conn.close()
207
-
208
- return {
209
- 'tier1': stats.get(1, 0),
210
- 'tier2': stats.get(2, 0),
211
- 'tier3': stats.get(3, 0)
212
- }
213
-
214
-
215
- class Tier2Compressor:
216
- """Compress memories to summary + key excerpts (Tier 2)."""
217
-
218
- def __init__(self, db_path: Path = DB_PATH):
219
- self.db_path = db_path
220
-
221
- def compress_to_tier2(self, memory_id: int) -> bool:
222
- """
223
- Compress memory to summary + excerpts.
224
-
225
- Args:
226
- memory_id: ID of memory to compress
227
-
228
- Returns:
229
- True if compression succeeded, False otherwise
230
- """
231
- conn = sqlite3.connect(self.db_path)
232
- cursor = conn.cursor()
233
-
234
- # Get full content
235
- cursor.execute('''
236
- SELECT content, summary, tier FROM memories WHERE id = ?
237
- ''', (memory_id,))
238
- result = cursor.fetchone()
239
-
240
- if not result:
241
- conn.close()
242
- return False
243
-
244
- content, existing_summary, current_tier = result
245
-
246
- # Skip if already compressed or in wrong tier
247
- if current_tier != 2:
248
- conn.close()
249
- return False
250
-
251
- # Check if already archived (don't re-compress)
252
- cursor.execute('''
253
- SELECT full_content FROM memory_archive WHERE memory_id = ?
254
- ''', (memory_id,))
255
- if cursor.fetchone():
256
- conn.close()
257
- return True # Already compressed
258
-
259
- # Try to parse as JSON (might already be compressed)
260
- try:
261
- parsed = json.loads(content)
262
- if isinstance(parsed, dict) and 'summary' in parsed:
263
- conn.close()
264
- return True # Already compressed
265
- except (json.JSONDecodeError, TypeError):
266
- pass # Not compressed yet
267
-
268
- # Generate/enhance summary if needed
269
- if not existing_summary or len(existing_summary) < 100:
270
- summary = self._generate_summary(content)
271
- else:
272
- summary = existing_summary
273
-
274
- # Extract key excerpts (important sentences, code blocks, lists)
275
- excerpts = self._extract_key_excerpts(content)
276
-
277
- # Store compressed version
278
- compressed_content = {
279
- 'summary': summary,
280
- 'excerpts': excerpts,
281
- 'original_length': len(content),
282
- 'compressed_at': datetime.now().isoformat()
283
- }
284
-
285
- # Move full content to archive table
286
- cursor.execute('''
287
- INSERT INTO memory_archive (memory_id, full_content, archived_at)
288
- VALUES (?, ?, CURRENT_TIMESTAMP)
289
- ''', (memory_id, content))
290
-
291
- # Update memory with compressed version
292
- cursor.execute('''
293
- UPDATE memories
294
- SET content = ?, tier = 2, updated_at = CURRENT_TIMESTAMP
295
- WHERE id = ?
296
- ''', (json.dumps(compressed_content), memory_id))
297
-
298
- conn.commit()
299
- conn.close()
300
- return True
301
-
302
- def _generate_summary(self, content: str, max_length: int = 300) -> str:
303
- """
304
- Generate extractive summary from content.
305
- Uses sentence scoring based on heuristics (no external LLM).
306
-
307
- Args:
308
- content: Full content text
309
- max_length: Maximum summary length in characters
310
-
311
- Returns:
312
- Extracted summary
313
- """
314
- # Split into sentences
315
- sentences = re.split(r'[.!?]+', content)
316
-
317
- # Score sentences by importance (simple heuristic)
318
- scored_sentences = []
319
-
320
- for i, sent in enumerate(sentences):
321
- sent = sent.strip()
322
- if len(sent) < 10:
323
- continue
324
-
325
- score = 0
326
-
327
- # Boost if contains tech terms
328
- tech_terms = ['api', 'database', 'auth', 'component', 'function',
329
- 'class', 'method', 'variable', 'error', 'bug', 'fix',
330
- 'implement', 'refactor', 'test', 'deploy']
331
- score += sum(1 for term in tech_terms if term in sent.lower())
332
-
333
- # Boost if at start or end (thesis/conclusion)
334
- if i == 0 or i == len(sentences) - 1:
335
- score += 2
336
-
337
- # Boost if contains numbers/specifics
338
- if re.search(r'\d+', sent):
339
- score += 1
340
-
341
- # Boost if contains important keywords
342
- important_keywords = ['important', 'critical', 'note', 'remember',
343
- 'key', 'main', 'primary', 'must', 'should']
344
- score += sum(2 for kw in important_keywords if kw in sent.lower())
345
-
346
- scored_sentences.append((score, sent))
347
-
348
- # Take top sentences up to max_length
349
- scored_sentences.sort(reverse=True, key=lambda x: x[0])
350
-
351
- summary_parts = []
352
- current_length = 0
353
-
354
- for score, sent in scored_sentences:
355
- if current_length + len(sent) > max_length:
356
- break
357
-
358
- summary_parts.append(sent)
359
- current_length += len(sent)
360
-
361
- if not summary_parts:
362
- # Fallback: take first sentence
363
- return sentences[0][:max_length] if sentences else content[:max_length]
364
-
365
- return '. '.join(summary_parts) + '.'
366
-
367
- def _extract_key_excerpts(self, content: str, max_excerpts: int = 3) -> List[str]:
368
- """
369
- Extract key excerpts (code blocks, lists, important paragraphs).
370
-
371
- Args:
372
- content: Full content text
373
- max_excerpts: Maximum number of excerpts to extract
374
-
375
- Returns:
376
- List of excerpt strings
377
- """
378
- excerpts = []
379
-
380
- # Extract code blocks (markdown or indented)
381
- code_blocks = re.findall(r'```[\s\S]*?```', content)
382
- excerpts.extend(code_blocks[:2]) # Max 2 code blocks
383
-
384
- # Extract bullet lists
385
- list_pattern = r'(?:^|\n)(?:[-*•]|\d+\.)\s+.+(?:\n(?:[-*•]|\d+\.)\s+.+)*'
386
- lists = re.findall(list_pattern, content, re.MULTILINE)
387
- if lists and len(excerpts) < max_excerpts:
388
- excerpts.extend(lists[:1]) # Max 1 list
389
-
390
- # Extract paragraphs with important keywords if we need more
391
- if len(excerpts) < max_excerpts:
392
- paragraphs = content.split('\n\n')
393
- important_keywords = ['important', 'critical', 'note', 'remember', 'key']
394
-
395
- for para in paragraphs:
396
- if len(excerpts) >= max_excerpts:
397
- break
398
-
399
- if any(kw in para.lower() for kw in important_keywords):
400
- # Truncate long paragraphs
401
- if len(para) > 200:
402
- para = para[:197] + '...'
403
- excerpts.append(para)
404
-
405
- # Truncate if too many
406
- return excerpts[:max_excerpts]
407
-
408
- def compress_all_tier2(self) -> int:
409
- """Compress all memories that are in Tier 2."""
410
- conn = sqlite3.connect(self.db_path)
411
- cursor = conn.cursor()
412
-
413
- cursor.execute('SELECT id FROM memories WHERE tier = 2')
414
- memory_ids = [row[0] for row in cursor.fetchall()]
415
- conn.close()
416
-
417
- compressed_count = 0
418
- for memory_id in memory_ids:
419
- if self.compress_to_tier2(memory_id):
420
- compressed_count += 1
421
-
422
- return compressed_count
423
-
424
-
425
- class Tier3Compressor:
426
- """Compress memories to bullet points only (Tier 3)."""
427
-
428
- def __init__(self, db_path: Path = DB_PATH):
429
- self.db_path = db_path
430
-
431
- def compress_to_tier3(self, memory_id: int) -> bool:
432
- """
433
- Compress memory to bullet points only.
434
-
435
- Args:
436
- memory_id: ID of memory to compress
437
-
438
- Returns:
439
- True if compression succeeded, False otherwise
440
- """
441
- conn = sqlite3.connect(self.db_path)
442
- cursor = conn.cursor()
443
-
444
- # Get Tier 2 compressed content
445
- cursor.execute('''
446
- SELECT content, tier FROM memories WHERE id = ?
447
- ''', (memory_id,))
448
- result = cursor.fetchone()
449
-
450
- if not result:
451
- conn.close()
452
- return False
453
-
454
- content, current_tier = result
455
-
456
- # Skip if in wrong tier
457
- if current_tier != 3:
458
- conn.close()
459
- return False
460
-
461
- # Try to parse as Tier 2 compressed content
462
- try:
463
- compressed_content = json.loads(content)
464
-
465
- # Check if already Tier 3
466
- if isinstance(compressed_content, dict) and 'bullets' in compressed_content:
467
- conn.close()
468
- return True # Already Tier 3
469
-
470
- # Get summary from Tier 2
471
- if isinstance(compressed_content, dict) and 'summary' in compressed_content:
472
- summary = compressed_content.get('summary', '')
473
- tier2_archived_at = compressed_content.get('compressed_at')
474
- original_length = compressed_content.get('original_length', 0)
475
- else:
476
- # Not Tier 2 format, treat as plain text
477
- summary = content
478
- tier2_archived_at = None
479
- original_length = len(content)
480
-
481
- except (json.JSONDecodeError, TypeError):
482
- # Not JSON, treat as plain text
483
- summary = content
484
- tier2_archived_at = None
485
- original_length = len(content)
486
-
487
- # Convert summary to bullet points (max 5)
488
- bullet_points = self._summarize_to_bullets(summary)
489
-
490
- # Ultra-compressed version
491
- ultra_compressed = {
492
- 'bullets': bullet_points,
493
- 'tier2_archived_at': tier2_archived_at,
494
- 'original_length': original_length,
495
- 'compressed_to_tier3_at': datetime.now().isoformat()
496
- }
497
-
498
- # Update memory
499
- cursor.execute('''
500
- UPDATE memories
501
- SET content = ?, tier = 3, updated_at = CURRENT_TIMESTAMP
502
- WHERE id = ?
503
- ''', (json.dumps(ultra_compressed), memory_id))
504
-
505
- conn.commit()
506
- conn.close()
507
- return True
508
-
509
- def _summarize_to_bullets(self, summary: str, max_bullets: int = 5) -> List[str]:
510
- """
511
- Convert summary to bullet points.
512
-
513
- Args:
514
- summary: Summary text
515
- max_bullets: Maximum number of bullets
516
-
517
- Returns:
518
- List of bullet point strings
519
- """
520
- # Split into sentences
521
- sentences = re.split(r'[.!?]+', summary)
522
-
523
- bullets = []
524
-
525
- for sent in sentences:
526
- sent = sent.strip()
527
-
528
- if len(sent) < 10:
529
- continue
530
-
531
- # Truncate long sentences
532
- if len(sent) > 80:
533
- sent = sent[:77] + '...'
534
-
535
- bullets.append(sent)
536
-
537
- if len(bullets) >= max_bullets:
538
- break
539
-
540
- return bullets if bullets else ['[No summary available]']
541
-
542
- def compress_all_tier3(self) -> int:
543
- """Compress all memories that are in Tier 3."""
544
- conn = sqlite3.connect(self.db_path)
545
- cursor = conn.cursor()
546
-
547
- cursor.execute('SELECT id FROM memories WHERE tier = 3')
548
- memory_ids = [row[0] for row in cursor.fetchall()]
549
- conn.close()
550
-
551
- compressed_count = 0
552
- for memory_id in memory_ids:
553
- if self.compress_to_tier3(memory_id):
554
- compressed_count += 1
555
-
556
- return compressed_count
557
-
558
-
559
- class ColdStorageManager:
560
- """Manage cold storage archives for very old memories."""
561
-
562
- def __init__(self, db_path: Path = DB_PATH, storage_path: Path = COLD_STORAGE_PATH):
563
- self.db_path = db_path
564
- self.storage_path = storage_path
565
- self.storage_path.mkdir(exist_ok=True)
566
- self.config = CompressionConfig()
567
-
568
- def move_to_cold_storage(self, memory_ids: List[int]) -> int:
569
- """
570
- Move archived memories to gzipped JSON file.
571
-
572
- Args:
573
- memory_ids: List of memory IDs to archive
574
-
575
- Returns:
576
- Number of memories archived
577
- """
578
- if not memory_ids:
579
- return 0
580
-
581
- conn = sqlite3.connect(self.db_path)
582
- cursor = conn.cursor()
583
-
584
- # Build placeholders for SQL query
585
- placeholders = ','.join('?' * len(memory_ids))
586
-
587
- # Get memories from archive table
588
- cursor.execute(f'''
589
- SELECT m.id, m.content, m.summary, m.tags, m.project_name,
590
- m.created_at, a.full_content
591
- FROM memories m
592
- LEFT JOIN memory_archive a ON m.id = a.memory_id
593
- WHERE m.id IN ({placeholders})
594
- ''', memory_ids)
595
-
596
- memories = cursor.fetchall()
597
-
598
- if not memories:
599
- conn.close()
600
- return 0
601
-
602
- # Build JSON export
603
- export_data = []
604
-
605
- for memory in memories:
606
- mem_id, content, summary, tags, project_name, created_at, full_content = memory
607
-
608
- export_data.append({
609
- 'id': mem_id,
610
- 'tier3_content': self._safe_json_load(content),
611
- 'summary': summary,
612
- 'tags': self._safe_json_load(tags) if tags else [],
613
- 'project': project_name,
614
- 'created_at': created_at,
615
- 'full_content': full_content # May be None if not archived
616
- })
617
-
618
- # Write to gzipped file
619
- filename = f"archive-{datetime.now().strftime('%Y-%m')}.json.gz"
620
- filepath = self.storage_path / filename
621
-
622
- # If file exists, append to it
623
- existing_data = []
624
- if filepath.exists():
625
- try:
626
- with gzip.open(filepath, 'rt', encoding='utf-8') as f:
627
- existing_data = json.load(f)
628
- except Exception:
629
- pass # File might be corrupted, start fresh
630
-
631
- # Merge with existing data (avoid duplicates)
632
- existing_ids = {item['id'] for item in existing_data}
633
- for item in export_data:
634
- if item['id'] not in existing_ids:
635
- existing_data.append(item)
636
-
637
- # Write combined data
638
- with gzip.open(filepath, 'wt', encoding='utf-8') as f:
639
- json.dump(existing_data, f, indent=2)
640
-
641
- # Delete from archive table (keep Tier 3 version in main table)
642
- cursor.executemany('DELETE FROM memory_archive WHERE memory_id = ?',
643
- [(mid,) for mid in memory_ids])
644
-
645
- conn.commit()
646
- conn.close()
647
-
648
- return len(export_data)
649
-
650
- def _safe_json_load(self, data: str) -> Any:
651
- """Safely load JSON data."""
652
- try:
653
- return json.loads(data)
654
- except (json.JSONDecodeError, TypeError):
655
- return data
656
-
657
- def restore_from_cold_storage(self, memory_id: int) -> Optional[str]:
658
- """
659
- Restore full content from cold storage archive.
660
-
661
- Args:
662
- memory_id: ID of memory to restore
663
-
664
- Returns:
665
- Full content if found, None otherwise
666
- """
667
- # Search all archive files
668
- for archive_file in self.storage_path.glob('archive-*.json.gz'):
669
- try:
670
- with gzip.open(archive_file, 'rt', encoding='utf-8') as f:
671
- data = json.load(f)
672
-
673
- for memory in data:
674
- if memory['id'] == memory_id:
675
- full_content = memory.get('full_content')
676
-
677
- if full_content:
678
- # Restore to archive table
679
- conn = sqlite3.connect(self.db_path)
680
- cursor = conn.cursor()
681
-
682
- cursor.execute('''
683
- INSERT OR REPLACE INTO memory_archive
684
- (memory_id, full_content, archived_at)
685
- VALUES (?, ?, CURRENT_TIMESTAMP)
686
- ''', (memory_id, full_content))
687
-
688
- conn.commit()
689
- conn.close()
690
-
691
- return full_content
692
- except Exception as e:
693
- print(f"Error reading archive {archive_file}: {e}")
694
- continue
695
-
696
- return None
697
-
698
- def get_cold_storage_candidates(self) -> List[int]:
699
- """Get memory IDs that are candidates for cold storage."""
700
- threshold_date = datetime.now() - timedelta(days=self.config.cold_storage_threshold_days)
701
-
702
- conn = sqlite3.connect(self.db_path)
703
- cursor = conn.cursor()
704
-
705
- cursor.execute('''
706
- SELECT id FROM memories
707
- WHERE tier = 3
708
- AND created_at < ?
709
- AND importance < 8
710
- ''', (threshold_date.isoformat(),))
711
-
712
- memory_ids = [row[0] for row in cursor.fetchall()]
713
- conn.close()
714
-
715
- return memory_ids
716
-
717
- def get_cold_storage_stats(self) -> Dict[str, Any]:
718
- """Get statistics about cold storage."""
719
- stats = {
720
- 'archive_count': 0,
721
- 'total_memories': 0,
722
- 'total_size_bytes': 0,
723
- 'archives': []
724
- }
725
-
726
- for archive_file in self.storage_path.glob('archive-*.json.gz'):
727
- try:
728
- size = archive_file.stat().st_size
729
-
730
- with gzip.open(archive_file, 'rt', encoding='utf-8') as f:
731
- data = json.load(f)
732
- memory_count = len(data)
733
-
734
- stats['archive_count'] += 1
735
- stats['total_memories'] += memory_count
736
- stats['total_size_bytes'] += size
737
-
738
- stats['archives'].append({
739
- 'filename': archive_file.name,
740
- 'memory_count': memory_count,
741
- 'size_bytes': size,
742
- 'size_mb': round(size / 1024 / 1024, 2)
743
- })
744
- except Exception:
745
- continue
746
-
747
- return stats
748
-
749
-
750
- class CompressionOrchestrator:
751
- """Main orchestrator for compression operations."""
752
-
753
- def __init__(self, db_path: Path = DB_PATH):
754
- self.db_path = db_path
755
- self.config = CompressionConfig()
756
- self.classifier = TierClassifier(db_path)
757
- self.tier2_compressor = Tier2Compressor(db_path)
758
- self.tier3_compressor = Tier3Compressor(db_path)
759
- self.cold_storage = ColdStorageManager(db_path)
760
-
761
- def run_full_compression(self) -> Dict[str, Any]:
762
- """
763
- Run full compression cycle: classify, compress, and archive.
764
-
765
- Returns:
766
- Statistics about compression operation
767
- """
768
- if not self.config.enabled:
769
- return {'status': 'disabled', 'message': 'Compression is disabled in config'}
770
-
771
- stats = {
772
- 'started_at': datetime.now().isoformat(),
773
- 'tier_updates': 0,
774
- 'tier2_compressed': 0,
775
- 'tier3_compressed': 0,
776
- 'cold_stored': 0,
777
- 'errors': []
778
- }
779
-
780
- try:
781
- # Step 1: Classify memories into tiers
782
- tier_updates = self.classifier.classify_memories()
783
- stats['tier_updates'] = len(tier_updates)
784
-
785
- # Step 2: Compress Tier 2 memories
786
- stats['tier2_compressed'] = self.tier2_compressor.compress_all_tier2()
787
-
788
- # Step 3: Compress Tier 3 memories
789
- stats['tier3_compressed'] = self.tier3_compressor.compress_all_tier3()
790
-
791
- # Step 4: Move old memories to cold storage
792
- candidates = self.cold_storage.get_cold_storage_candidates()
793
- if candidates:
794
- stats['cold_stored'] = self.cold_storage.move_to_cold_storage(candidates)
795
-
796
- # Get final tier stats
797
- stats['tier_stats'] = self.classifier.get_tier_stats()
798
-
799
- # Calculate space savings
800
- stats['space_savings'] = self._calculate_space_savings()
801
-
802
- except Exception as e:
803
- stats['errors'].append(str(e))
804
-
805
- stats['completed_at'] = datetime.now().isoformat()
806
- return stats
807
-
808
- def _calculate_space_savings(self) -> Dict[str, Any]:
809
- """Calculate estimated space savings from compression."""
810
- conn = sqlite3.connect(self.db_path)
811
- cursor = conn.cursor()
812
-
813
- # Get size of compressed content
814
- cursor.execute('''
815
- SELECT
816
- tier,
817
- COUNT(*) as count,
818
- SUM(LENGTH(content)) as total_size
819
- FROM memories
820
- GROUP BY tier
821
- ''')
822
-
823
- tier_sizes = {}
824
- for tier, count, total_size in cursor.fetchall():
825
- tier_sizes[tier] = {
826
- 'count': count,
827
- 'size_bytes': total_size or 0
828
- }
829
-
830
- # Get size of archived content
831
- cursor.execute('''
832
- SELECT
833
- COUNT(*) as count,
834
- SUM(LENGTH(full_content)) as total_size
835
- FROM memory_archive
836
- ''')
837
- archive_count, archive_size = cursor.fetchone()
838
-
839
- conn.close()
840
-
841
- # Estimate original size if all were Tier 1
842
- tier1_avg = tier_sizes.get(1, {}).get('size_bytes', 50000) / max(tier_sizes.get(1, {}).get('count', 1), 1)
843
- total_memories = sum(t.get('count', 0) for t in tier_sizes.values())
844
- estimated_original = int(tier1_avg * total_memories)
845
-
846
- current_size = sum(t.get('size_bytes', 0) for t in tier_sizes.values())
847
-
848
- return {
849
- 'estimated_original_bytes': estimated_original,
850
- 'current_size_bytes': current_size,
851
- 'savings_bytes': estimated_original - current_size,
852
- 'savings_percent': round((1 - current_size / max(estimated_original, 1)) * 100, 1),
853
- 'tier_breakdown': tier_sizes,
854
- 'archive_count': archive_count or 0,
855
- 'archive_size_bytes': archive_size or 0
856
- }
857
-
858
-
859
- # CLI Interface
860
- if __name__ == "__main__":
861
- import sys
862
-
863
- if len(sys.argv) < 2:
864
- print("Progressive Summarization Compression for SuperLocalMemory\n")
865
- print("Usage:")
866
- print(" python compression.py classify # Classify memories into tiers")
867
- print(" python compression.py compress # Run full compression cycle")
868
- print(" python compression.py stats # Show compression statistics")
869
- print(" python compression.py tier2 <id> # Compress specific memory to Tier 2")
870
- print(" python compression.py tier3 <id> # Compress specific memory to Tier 3")
871
- print(" python compression.py cold-storage # Move old memories to cold storage")
872
- print(" python compression.py restore <id> # Restore memory from cold storage")
873
- print(" python compression.py init-config # Initialize compression config")
874
- sys.exit(0)
875
-
876
- command = sys.argv[1]
877
- orchestrator = CompressionOrchestrator()
878
-
879
- if command == "classify":
880
- classifier = TierClassifier()
881
- updates = classifier.classify_memories()
882
- print(f"Classified {len(updates)} memories")
883
-
884
- stats = classifier.get_tier_stats()
885
- print(f"\nTier breakdown:")
886
- print(f" Tier 1 (Full content): {stats['tier1']} memories")
887
- print(f" Tier 2 (Summary+excerpts): {stats['tier2']} memories")
888
- print(f" Tier 3 (Bullets only): {stats['tier3']} memories")
889
-
890
- elif command == "compress":
891
- print("Running full compression cycle...")
892
- stats = orchestrator.run_full_compression()
893
-
894
- print(f"\nCompression Results:")
895
- print(f" Tier updates: {stats['tier_updates']}")
896
- print(f" Tier 2 compressed: {stats['tier2_compressed']}")
897
- print(f" Tier 3 compressed: {stats['tier3_compressed']}")
898
- print(f" Moved to cold storage: {stats['cold_stored']}")
899
-
900
- if 'space_savings' in stats:
901
- savings = stats['space_savings']
902
- print(f"\nSpace Savings:")
903
- print(f" Original size: {savings['estimated_original_bytes']:,} bytes")
904
- print(f" Current size: {savings['current_size_bytes']:,} bytes")
905
- print(f" Savings: {savings['savings_bytes']:,} bytes ({savings['savings_percent']}%)")
906
-
907
- if stats.get('errors'):
908
- print(f"\nErrors: {stats['errors']}")
909
-
910
- elif command == "stats":
911
- classifier = TierClassifier()
912
- tier_stats = classifier.get_tier_stats()
913
-
914
- cold_storage = ColdStorageManager()
915
- cold_stats = cold_storage.get_cold_storage_stats()
916
-
917
- savings = orchestrator._calculate_space_savings()
918
-
919
- print("Compression Statistics\n")
920
- print("Tier Breakdown:")
921
- print(f" Tier 1 (Full content): {tier_stats['tier1']} memories")
922
- print(f" Tier 2 (Summary+excerpts): {tier_stats['tier2']} memories")
923
- print(f" Tier 3 (Bullets only): {tier_stats['tier3']} memories")
924
-
925
- print(f"\nCold Storage:")
926
- print(f" Archive files: {cold_stats['archive_count']}")
927
- print(f" Total memories: {cold_stats['total_memories']}")
928
- print(f" Total size: {cold_stats['total_size_bytes']:,} bytes")
929
-
930
- print(f"\nSpace Savings:")
931
- print(f" Estimated original: {savings['estimated_original_bytes']:,} bytes")
932
- print(f" Current size: {savings['current_size_bytes']:,} bytes")
933
- print(f" Savings: {savings['savings_bytes']:,} bytes ({savings['savings_percent']}%)")
934
-
935
- elif command == "tier2" and len(sys.argv) >= 3:
936
- try:
937
- memory_id = int(sys.argv[2])
938
- compressor = Tier2Compressor()
939
- if compressor.compress_to_tier2(memory_id):
940
- print(f"Memory #{memory_id} compressed to Tier 2")
941
- else:
942
- print(f"Failed to compress memory #{memory_id}")
943
- except ValueError:
944
- print("Error: Memory ID must be a number")
945
-
946
- elif command == "tier3" and len(sys.argv) >= 3:
947
- try:
948
- memory_id = int(sys.argv[2])
949
- compressor = Tier3Compressor()
950
- if compressor.compress_to_tier3(memory_id):
951
- print(f"Memory #{memory_id} compressed to Tier 3")
952
- else:
953
- print(f"Failed to compress memory #{memory_id}")
954
- except ValueError:
955
- print("Error: Memory ID must be a number")
956
-
957
- elif command == "cold-storage":
958
- cold_storage = ColdStorageManager()
959
- candidates = cold_storage.get_cold_storage_candidates()
960
-
961
- if not candidates:
962
- print("No memories ready for cold storage")
963
- else:
964
- print(f"Moving {len(candidates)} memories to cold storage...")
965
- count = cold_storage.move_to_cold_storage(candidates)
966
- print(f"Archived {count} memories")
967
-
968
- elif command == "restore" and len(sys.argv) >= 3:
969
- try:
970
- memory_id = int(sys.argv[2])
971
- cold_storage = ColdStorageManager()
972
- content = cold_storage.restore_from_cold_storage(memory_id)
973
-
974
- if content:
975
- print(f"Memory #{memory_id} restored from cold storage")
976
- else:
977
- print(f"Memory #{memory_id} not found in cold storage")
978
- except ValueError:
979
- print("Error: Memory ID must be a number")
980
-
981
- elif command == "init-config":
982
- config = CompressionConfig()
983
- config.initialize_defaults()
984
- print("Compression configuration initialized")
985
- print(json.dumps(config.compression_settings, indent=2))
986
-
987
- else:
988
- print(f"Unknown command: {command}")
989
- sys.exit(1)