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
@@ -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)