superlocalmemory 2.8.6 → 3.0.1
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.
- package/LICENSE +9 -1
- package/NOTICE +63 -0
- package/README.md +165 -480
- package/bin/slm +17 -449
- package/bin/slm-npm +62 -48
- package/conftest.py +5 -0
- package/docs/api-reference.md +284 -0
- package/docs/architecture.md +149 -0
- package/docs/auto-memory.md +150 -0
- package/docs/cli-reference.md +276 -0
- package/docs/compliance.md +191 -0
- package/docs/configuration.md +182 -0
- package/docs/getting-started.md +102 -0
- package/docs/ide-setup.md +261 -0
- package/docs/mcp-tools.md +220 -0
- package/docs/migration-from-v2.md +170 -0
- package/docs/profiles.md +173 -0
- package/docs/troubleshooting.md +310 -0
- package/{configs → ide/configs}/antigravity-mcp.json +3 -3
- package/ide/configs/chatgpt-desktop-mcp.json +16 -0
- package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
- package/{configs → ide/configs}/codex-mcp.toml +4 -4
- package/{configs → ide/configs}/continue-mcp.yaml +4 -3
- package/{configs → ide/configs}/continue-skills.yaml +6 -6
- package/ide/configs/cursor-mcp.json +15 -0
- package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
- package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
- package/{configs → ide/configs}/opencode-mcp.json +2 -2
- package/{configs → ide/configs}/perplexity-mcp.json +2 -2
- package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
- package/{configs → ide/configs}/windsurf-mcp.json +3 -3
- package/{configs → ide/configs}/zed-mcp.json +2 -2
- package/{hooks → ide/hooks}/context-hook.js +9 -20
- package/ide/hooks/memory-list-skill.js +70 -0
- package/ide/hooks/memory-profile-skill.js +101 -0
- package/ide/hooks/memory-recall-skill.js +62 -0
- package/ide/hooks/memory-remember-skill.js +68 -0
- package/ide/hooks/memory-reset-skill.js +160 -0
- package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
- package/ide/integrations/langchain/README.md +106 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
- package/ide/integrations/langchain/pyproject.toml +38 -0
- package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
- package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
- package/ide/integrations/langchain/tests/test_security.py +117 -0
- package/ide/integrations/llamaindex/README.md +81 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
- package/ide/integrations/llamaindex/pyproject.toml +43 -0
- package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
- package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
- package/ide/integrations/llamaindex/tests/test_security.py +241 -0
- package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
- package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
- package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
- package/package.json +13 -22
- package/pyproject.toml +85 -0
- package/scripts/build-dmg.sh +417 -0
- package/scripts/install-skills.ps1 +334 -0
- package/scripts/postinstall.js +2 -2
- package/scripts/start-dashboard.ps1 +52 -0
- package/scripts/start-dashboard.sh +41 -0
- package/scripts/sync-wiki.ps1 +127 -0
- package/scripts/sync-wiki.sh +82 -0
- package/scripts/test-dmg.sh +161 -0
- package/scripts/test-npm-package.ps1 +252 -0
- package/scripts/test-npm-package.sh +207 -0
- package/scripts/verify-install.ps1 +294 -0
- package/scripts/verify-install.sh +266 -0
- package/src/superlocalmemory/__init__.py +0 -0
- package/src/superlocalmemory/attribution/__init__.py +9 -0
- package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
- package/src/superlocalmemory/attribution/signer.py +153 -0
- package/src/superlocalmemory/attribution/watermark.py +189 -0
- package/src/superlocalmemory/cli/__init__.py +5 -0
- package/src/superlocalmemory/cli/commands.py +245 -0
- package/src/superlocalmemory/cli/main.py +89 -0
- package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
- package/src/superlocalmemory/cli/post_install.py +99 -0
- package/src/superlocalmemory/cli/setup_wizard.py +129 -0
- package/src/superlocalmemory/compliance/__init__.py +0 -0
- package/src/superlocalmemory/compliance/abac.py +204 -0
- package/src/superlocalmemory/compliance/audit.py +314 -0
- package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
- package/src/superlocalmemory/compliance/gdpr.py +294 -0
- package/src/superlocalmemory/compliance/lifecycle.py +158 -0
- package/src/superlocalmemory/compliance/retention.py +232 -0
- package/src/superlocalmemory/compliance/scheduler.py +148 -0
- package/src/superlocalmemory/core/__init__.py +0 -0
- package/src/superlocalmemory/core/config.py +391 -0
- package/src/superlocalmemory/core/embeddings.py +293 -0
- package/src/superlocalmemory/core/engine.py +701 -0
- package/src/superlocalmemory/core/hooks.py +65 -0
- package/src/superlocalmemory/core/maintenance.py +172 -0
- package/src/superlocalmemory/core/modes.py +140 -0
- package/src/superlocalmemory/core/profiles.py +234 -0
- package/src/superlocalmemory/core/registry.py +117 -0
- package/src/superlocalmemory/dynamics/__init__.py +0 -0
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
- package/src/superlocalmemory/encoding/__init__.py +0 -0
- package/src/superlocalmemory/encoding/consolidator.py +485 -0
- package/src/superlocalmemory/encoding/emotional.py +125 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
- package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
- package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
- package/src/superlocalmemory/encoding/foresight.py +91 -0
- package/src/superlocalmemory/encoding/graph_builder.py +302 -0
- package/src/superlocalmemory/encoding/observation_builder.py +160 -0
- package/src/superlocalmemory/encoding/scene_builder.py +183 -0
- package/src/superlocalmemory/encoding/signal_inference.py +90 -0
- package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
- package/src/superlocalmemory/encoding/type_router.py +235 -0
- package/src/superlocalmemory/hooks/__init__.py +3 -0
- package/src/superlocalmemory/hooks/auto_capture.py +111 -0
- package/src/superlocalmemory/hooks/auto_recall.py +93 -0
- package/src/superlocalmemory/hooks/ide_connector.py +204 -0
- package/src/superlocalmemory/hooks/rules_engine.py +99 -0
- package/src/superlocalmemory/infra/__init__.py +3 -0
- package/src/superlocalmemory/infra/auth_middleware.py +82 -0
- package/src/superlocalmemory/infra/backup.py +317 -0
- package/src/superlocalmemory/infra/cache_manager.py +267 -0
- package/src/superlocalmemory/infra/event_bus.py +381 -0
- package/src/superlocalmemory/infra/rate_limiter.py +135 -0
- package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
- package/src/superlocalmemory/learning/__init__.py +0 -0
- package/src/superlocalmemory/learning/adaptive.py +172 -0
- package/src/superlocalmemory/learning/behavioral.py +490 -0
- package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
- package/src/superlocalmemory/learning/bootstrap.py +298 -0
- package/src/superlocalmemory/learning/cross_project.py +399 -0
- package/src/superlocalmemory/learning/database.py +376 -0
- package/src/superlocalmemory/learning/engagement.py +323 -0
- package/src/superlocalmemory/learning/features.py +138 -0
- package/src/superlocalmemory/learning/feedback.py +316 -0
- package/src/superlocalmemory/learning/outcomes.py +255 -0
- package/src/superlocalmemory/learning/project_context.py +366 -0
- package/src/superlocalmemory/learning/ranker.py +155 -0
- package/src/superlocalmemory/learning/source_quality.py +303 -0
- package/src/superlocalmemory/learning/workflows.py +309 -0
- package/src/superlocalmemory/llm/__init__.py +0 -0
- package/src/superlocalmemory/llm/backbone.py +316 -0
- package/src/superlocalmemory/math/__init__.py +0 -0
- package/src/superlocalmemory/math/fisher.py +356 -0
- package/src/superlocalmemory/math/langevin.py +398 -0
- package/src/superlocalmemory/math/sheaf.py +257 -0
- package/src/superlocalmemory/mcp/__init__.py +0 -0
- package/src/superlocalmemory/mcp/resources.py +245 -0
- package/src/superlocalmemory/mcp/server.py +61 -0
- package/src/superlocalmemory/mcp/tools.py +18 -0
- package/src/superlocalmemory/mcp/tools_core.py +305 -0
- package/src/superlocalmemory/mcp/tools_v28.py +223 -0
- package/src/superlocalmemory/mcp/tools_v3.py +286 -0
- package/src/superlocalmemory/retrieval/__init__.py +0 -0
- package/src/superlocalmemory/retrieval/agentic.py +295 -0
- package/src/superlocalmemory/retrieval/ann_index.py +223 -0
- package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
- package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
- package/src/superlocalmemory/retrieval/engine.py +390 -0
- package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
- package/src/superlocalmemory/retrieval/fusion.py +78 -0
- package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
- package/src/superlocalmemory/retrieval/reranker.py +154 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
- package/src/superlocalmemory/retrieval/strategy.py +96 -0
- package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
- package/src/superlocalmemory/server/__init__.py +1 -0
- package/src/superlocalmemory/server/api.py +248 -0
- package/src/superlocalmemory/server/routes/__init__.py +4 -0
- package/src/superlocalmemory/server/routes/agents.py +107 -0
- package/src/superlocalmemory/server/routes/backup.py +91 -0
- package/src/superlocalmemory/server/routes/behavioral.py +127 -0
- package/src/superlocalmemory/server/routes/compliance.py +160 -0
- package/src/superlocalmemory/server/routes/data_io.py +188 -0
- package/src/superlocalmemory/server/routes/events.py +183 -0
- package/src/superlocalmemory/server/routes/helpers.py +85 -0
- package/src/superlocalmemory/server/routes/learning.py +273 -0
- package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
- package/src/superlocalmemory/server/routes/memories.py +399 -0
- package/src/superlocalmemory/server/routes/profiles.py +219 -0
- package/src/superlocalmemory/server/routes/stats.py +346 -0
- package/src/superlocalmemory/server/routes/v3_api.py +365 -0
- package/src/superlocalmemory/server/routes/ws.py +82 -0
- package/src/superlocalmemory/server/security_middleware.py +57 -0
- package/src/superlocalmemory/server/ui.py +245 -0
- package/src/superlocalmemory/storage/__init__.py +0 -0
- package/src/superlocalmemory/storage/access_control.py +182 -0
- package/src/superlocalmemory/storage/database.py +594 -0
- package/src/superlocalmemory/storage/migrations.py +303 -0
- package/src/superlocalmemory/storage/models.py +406 -0
- package/src/superlocalmemory/storage/schema.py +726 -0
- package/src/superlocalmemory/storage/v2_migrator.py +317 -0
- package/src/superlocalmemory/trust/__init__.py +0 -0
- package/src/superlocalmemory/trust/gate.py +130 -0
- package/src/superlocalmemory/trust/provenance.py +124 -0
- package/src/superlocalmemory/trust/scorer.py +347 -0
- package/src/superlocalmemory/trust/signals.py +153 -0
- package/ui/index.html +278 -5
- package/ui/js/auto-settings.js +70 -0
- package/ui/js/dashboard.js +90 -0
- package/ui/js/fact-detail.js +92 -0
- package/ui/js/feedback.js +2 -2
- package/ui/js/ide-status.js +102 -0
- package/ui/js/math-health.js +98 -0
- package/ui/js/recall-lab.js +127 -0
- package/ui/js/settings.js +2 -2
- package/ui/js/trust-dashboard.js +73 -0
- package/api_server.py +0 -724
- package/bin/aider-smart +0 -72
- package/bin/superlocalmemoryv2-learning +0 -4
- package/bin/superlocalmemoryv2-list +0 -3
- package/bin/superlocalmemoryv2-patterns +0 -4
- package/bin/superlocalmemoryv2-profile +0 -3
- package/bin/superlocalmemoryv2-recall +0 -3
- package/bin/superlocalmemoryv2-remember +0 -3
- package/bin/superlocalmemoryv2-reset +0 -3
- package/bin/superlocalmemoryv2-status +0 -3
- package/configs/chatgpt-desktop-mcp.json +0 -16
- package/configs/cursor-mcp.json +0 -15
- package/hooks/memory-list-skill.js +0 -139
- package/hooks/memory-profile-skill.js +0 -273
- package/hooks/memory-recall-skill.js +0 -114
- package/hooks/memory-remember-skill.js +0 -127
- package/hooks/memory-reset-skill.js +0 -274
- package/mcp_server.py +0 -1808
- package/requirements-core.txt +0 -22
- package/requirements-learning.txt +0 -12
- package/requirements.txt +0 -12
- package/src/agent_registry.py +0 -411
- package/src/auth_middleware.py +0 -61
- package/src/auto_backup.py +0 -459
- package/src/behavioral/__init__.py +0 -49
- package/src/behavioral/behavioral_listener.py +0 -203
- package/src/behavioral/behavioral_patterns.py +0 -275
- package/src/behavioral/cross_project_transfer.py +0 -206
- package/src/behavioral/outcome_inference.py +0 -194
- package/src/behavioral/outcome_tracker.py +0 -193
- package/src/behavioral/tests/__init__.py +0 -4
- package/src/behavioral/tests/test_behavioral_integration.py +0 -108
- package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
- package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
- package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
- package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
- package/src/behavioral/tests/test_outcome_inference.py +0 -107
- package/src/behavioral/tests/test_outcome_tracker.py +0 -96
- package/src/cache_manager.py +0 -518
- package/src/compliance/__init__.py +0 -48
- package/src/compliance/abac_engine.py +0 -149
- package/src/compliance/abac_middleware.py +0 -116
- package/src/compliance/audit_db.py +0 -215
- package/src/compliance/audit_logger.py +0 -148
- package/src/compliance/retention_manager.py +0 -289
- package/src/compliance/retention_scheduler.py +0 -186
- package/src/compliance/tests/__init__.py +0 -4
- package/src/compliance/tests/test_abac_enforcement.py +0 -95
- package/src/compliance/tests/test_abac_engine.py +0 -124
- package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
- package/src/compliance/tests/test_audit_db.py +0 -123
- package/src/compliance/tests/test_audit_logger.py +0 -98
- package/src/compliance/tests/test_mcp_audit.py +0 -128
- package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
- package/src/compliance/tests/test_retention_manager.py +0 -131
- package/src/compliance/tests/test_retention_scheduler.py +0 -99
- package/src/compression/__init__.py +0 -25
- package/src/compression/cli.py +0 -150
- package/src/compression/cold_storage.py +0 -217
- package/src/compression/config.py +0 -72
- package/src/compression/orchestrator.py +0 -133
- package/src/compression/tier2_compressor.py +0 -228
- package/src/compression/tier3_compressor.py +0 -153
- package/src/compression/tier_classifier.py +0 -148
- package/src/db_connection_manager.py +0 -536
- package/src/embedding_engine.py +0 -63
- package/src/embeddings/__init__.py +0 -47
- package/src/embeddings/cache.py +0 -70
- package/src/embeddings/cli.py +0 -113
- package/src/embeddings/constants.py +0 -47
- package/src/embeddings/database.py +0 -91
- package/src/embeddings/engine.py +0 -247
- package/src/embeddings/model_loader.py +0 -145
- package/src/event_bus.py +0 -562
- package/src/graph/__init__.py +0 -36
- package/src/graph/build_helpers.py +0 -74
- package/src/graph/cli.py +0 -87
- package/src/graph/cluster_builder.py +0 -188
- package/src/graph/cluster_summary.py +0 -148
- package/src/graph/constants.py +0 -47
- package/src/graph/edge_builder.py +0 -162
- package/src/graph/entity_extractor.py +0 -95
- package/src/graph/graph_core.py +0 -226
- package/src/graph/graph_search.py +0 -231
- package/src/graph/hierarchical.py +0 -207
- package/src/graph/schema.py +0 -99
- package/src/graph_engine.py +0 -52
- package/src/hnsw_index.py +0 -628
- package/src/hybrid_search.py +0 -46
- package/src/learning/__init__.py +0 -217
- package/src/learning/adaptive_ranker.py +0 -682
- package/src/learning/bootstrap/__init__.py +0 -69
- package/src/learning/bootstrap/constants.py +0 -93
- package/src/learning/bootstrap/db_queries.py +0 -316
- package/src/learning/bootstrap/sampling.py +0 -82
- package/src/learning/bootstrap/text_utils.py +0 -71
- package/src/learning/cross_project_aggregator.py +0 -857
- package/src/learning/db/__init__.py +0 -40
- package/src/learning/db/constants.py +0 -44
- package/src/learning/db/schema.py +0 -279
- package/src/learning/engagement_tracker.py +0 -628
- package/src/learning/feature_extractor.py +0 -708
- package/src/learning/feedback_collector.py +0 -806
- package/src/learning/learning_db.py +0 -915
- package/src/learning/project_context_manager.py +0 -572
- package/src/learning/ranking/__init__.py +0 -33
- package/src/learning/ranking/constants.py +0 -84
- package/src/learning/ranking/helpers.py +0 -278
- package/src/learning/source_quality_scorer.py +0 -676
- package/src/learning/synthetic_bootstrap.py +0 -755
- package/src/learning/tests/test_adaptive_ranker.py +0 -325
- package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
- package/src/learning/tests/test_aggregator.py +0 -306
- package/src/learning/tests/test_auto_retrain_v28.py +0 -35
- package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
- package/src/learning/tests/test_feature_extractor_v28.py +0 -93
- package/src/learning/tests/test_feedback_collector.py +0 -294
- package/src/learning/tests/test_learning_db.py +0 -602
- package/src/learning/tests/test_learning_db_v28.py +0 -110
- package/src/learning/tests/test_learning_init_v28.py +0 -48
- package/src/learning/tests/test_outcome_signals.py +0 -48
- package/src/learning/tests/test_project_context.py +0 -292
- package/src/learning/tests/test_schema_migration.py +0 -319
- package/src/learning/tests/test_signal_inference.py +0 -397
- package/src/learning/tests/test_source_quality.py +0 -351
- package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
- package/src/learning/tests/test_workflow_miner.py +0 -318
- package/src/learning/workflow_pattern_miner.py +0 -655
- package/src/lifecycle/__init__.py +0 -54
- package/src/lifecycle/bounded_growth.py +0 -239
- package/src/lifecycle/compaction_engine.py +0 -226
- package/src/lifecycle/lifecycle_engine.py +0 -355
- package/src/lifecycle/lifecycle_evaluator.py +0 -257
- package/src/lifecycle/lifecycle_scheduler.py +0 -130
- package/src/lifecycle/retention_policy.py +0 -285
- package/src/lifecycle/tests/test_bounded_growth.py +0 -193
- package/src/lifecycle/tests/test_compaction.py +0 -179
- package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
- package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
- package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
- package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
- package/src/lifecycle/tests/test_mcp_compact.py +0 -149
- package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
- package/src/lifecycle/tests/test_retention_policy.py +0 -162
- package/src/mcp_tools_v28.py +0 -281
- package/src/memory/__init__.py +0 -36
- package/src/memory/cli.py +0 -205
- package/src/memory/constants.py +0 -39
- package/src/memory/helpers.py +0 -28
- package/src/memory/schema.py +0 -166
- package/src/memory-profiles.py +0 -595
- package/src/memory-reset.py +0 -491
- package/src/memory_compression.py +0 -989
- package/src/memory_store_v2.py +0 -1155
- package/src/migrate_v1_to_v2.py +0 -629
- package/src/pattern_learner.py +0 -34
- package/src/patterns/__init__.py +0 -24
- package/src/patterns/analyzers.py +0 -251
- package/src/patterns/learner.py +0 -271
- package/src/patterns/scoring.py +0 -171
- package/src/patterns/store.py +0 -225
- package/src/patterns/terminology.py +0 -140
- package/src/provenance_tracker.py +0 -312
- package/src/qualixar_attribution.py +0 -139
- package/src/qualixar_watermark.py +0 -78
- package/src/query_optimizer.py +0 -511
- package/src/rate_limiter.py +0 -83
- package/src/search/__init__.py +0 -20
- package/src/search/cli.py +0 -77
- package/src/search/constants.py +0 -26
- package/src/search/engine.py +0 -241
- package/src/search/fusion.py +0 -122
- package/src/search/index_loader.py +0 -114
- package/src/search/methods.py +0 -162
- package/src/search_engine_v2.py +0 -401
- package/src/setup_validator.py +0 -482
- package/src/subscription_manager.py +0 -391
- package/src/tree/__init__.py +0 -59
- package/src/tree/builder.py +0 -185
- package/src/tree/nodes.py +0 -202
- package/src/tree/queries.py +0 -257
- package/src/tree/schema.py +0 -80
- package/src/tree_manager.py +0 -19
- package/src/trust/__init__.py +0 -45
- package/src/trust/constants.py +0 -66
- package/src/trust/queries.py +0 -157
- package/src/trust/schema.py +0 -95
- package/src/trust/scorer.py +0 -299
- package/src/trust/signals.py +0 -95
- package/src/trust_scorer.py +0 -44
- package/ui/app.js +0 -1588
- package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
- package/ui/js/graph-cytoscape.js +0 -1168
- package/ui/js/graph-d3-backup.js +0 -32
- package/ui/js/graph.js +0 -32
- package/ui_server.py +0 -286
- /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
- /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
- /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
- /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
- /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
- /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
- /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
- /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
- /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
- /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
- /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
- /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
- /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
- /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
- /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
- /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
- /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
- /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
- /package/{completions → ide/completions}/slm.bash +0 -0
- /package/{completions → ide/completions}/slm.zsh +0 -0
- /package/{configs → ide/configs}/cody-commands.json +0 -0
- /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
- /package/{install.ps1 → scripts/install.ps1} +0 -0
- /package/{install.sh → scripts/install.sh} +0 -0
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Memory lifecycle state machine with formal transition rules.
|
|
4
|
-
|
|
5
|
-
State Machine:
|
|
6
|
-
ACTIVE -> WARM -> COLD -> ARCHIVED -> TOMBSTONED
|
|
7
|
-
|
|
8
|
-
Reactivation allowed from WARM, COLD, ARCHIVED back to ACTIVE.
|
|
9
|
-
TOMBSTONED is terminal (deletion only).
|
|
10
|
-
|
|
11
|
-
Each transition is recorded in lifecycle_history (JSON array) for auditability.
|
|
12
|
-
Thread-safe via threading.Lock() around read-modify-write operations.
|
|
13
|
-
"""
|
|
14
|
-
import sqlite3
|
|
15
|
-
import json
|
|
16
|
-
import threading
|
|
17
|
-
from datetime import datetime
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
from typing import Optional, Dict, Any, List
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class LifecycleEngine:
|
|
23
|
-
"""Manages memory lifecycle states: ACTIVE -> WARM -> COLD -> ARCHIVED -> TOMBSTONED."""
|
|
24
|
-
|
|
25
|
-
STATES = ("active", "warm", "cold", "archived", "tombstoned")
|
|
26
|
-
|
|
27
|
-
TRANSITIONS = {
|
|
28
|
-
"active": ["warm"],
|
|
29
|
-
"warm": ["active", "cold"],
|
|
30
|
-
"cold": ["active", "archived"],
|
|
31
|
-
"archived": ["active", "tombstoned"],
|
|
32
|
-
"tombstoned": [], # Terminal state
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
def __init__(self, db_path: Optional[str] = None, config_path: Optional[str] = None):
|
|
36
|
-
if db_path is None:
|
|
37
|
-
db_path = Path.home() / ".claude-memory" / "memory.db"
|
|
38
|
-
self._db_path = str(db_path)
|
|
39
|
-
self._config_path = config_path
|
|
40
|
-
self._lock = threading.Lock()
|
|
41
|
-
self._ensure_columns()
|
|
42
|
-
|
|
43
|
-
def _get_connection(self) -> sqlite3.Connection:
|
|
44
|
-
"""Get a SQLite connection to memory.db."""
|
|
45
|
-
conn = sqlite3.connect(self._db_path)
|
|
46
|
-
conn.row_factory = sqlite3.Row
|
|
47
|
-
return conn
|
|
48
|
-
|
|
49
|
-
def _ensure_columns(self) -> None:
|
|
50
|
-
"""Ensure v2.8 lifecycle columns exist in memories table."""
|
|
51
|
-
try:
|
|
52
|
-
conn = self._get_connection()
|
|
53
|
-
try:
|
|
54
|
-
cursor = conn.cursor()
|
|
55
|
-
cursor.execute("PRAGMA table_info(memories)")
|
|
56
|
-
existing = {row[1] for row in cursor.fetchall()}
|
|
57
|
-
v28_cols = [
|
|
58
|
-
("lifecycle_state", "TEXT DEFAULT 'active'"),
|
|
59
|
-
("lifecycle_updated_at", "TIMESTAMP"),
|
|
60
|
-
("lifecycle_history", "TEXT DEFAULT '[]'"),
|
|
61
|
-
("access_level", "TEXT DEFAULT 'public'"),
|
|
62
|
-
]
|
|
63
|
-
for col_name, col_type in v28_cols:
|
|
64
|
-
if col_name not in existing:
|
|
65
|
-
try:
|
|
66
|
-
cursor.execute(
|
|
67
|
-
f"ALTER TABLE memories ADD COLUMN {col_name} {col_type}"
|
|
68
|
-
)
|
|
69
|
-
except sqlite3.OperationalError:
|
|
70
|
-
pass
|
|
71
|
-
conn.commit()
|
|
72
|
-
finally:
|
|
73
|
-
conn.close()
|
|
74
|
-
except Exception:
|
|
75
|
-
pass # Graceful degradation — don't block engine init
|
|
76
|
-
|
|
77
|
-
def is_valid_transition(self, from_state: str, to_state: str) -> bool:
|
|
78
|
-
"""Check if a state transition is valid per the state machine.
|
|
79
|
-
|
|
80
|
-
Args:
|
|
81
|
-
from_state: Current lifecycle state
|
|
82
|
-
to_state: Target lifecycle state
|
|
83
|
-
|
|
84
|
-
Returns:
|
|
85
|
-
True if the transition is allowed, False otherwise
|
|
86
|
-
"""
|
|
87
|
-
if from_state not in self.TRANSITIONS:
|
|
88
|
-
return False
|
|
89
|
-
return to_state in self.TRANSITIONS[from_state]
|
|
90
|
-
|
|
91
|
-
def get_memory_state(self, memory_id: int) -> Optional[str]:
|
|
92
|
-
"""Get the current lifecycle state of a memory.
|
|
93
|
-
|
|
94
|
-
Args:
|
|
95
|
-
memory_id: The memory's database ID
|
|
96
|
-
|
|
97
|
-
Returns:
|
|
98
|
-
The lifecycle state string, or None if memory not found
|
|
99
|
-
"""
|
|
100
|
-
conn = self._get_connection()
|
|
101
|
-
try:
|
|
102
|
-
try:
|
|
103
|
-
row = conn.execute(
|
|
104
|
-
"SELECT lifecycle_state FROM memories WHERE id = ?",
|
|
105
|
-
(memory_id,),
|
|
106
|
-
).fetchone()
|
|
107
|
-
except sqlite3.OperationalError as e:
|
|
108
|
-
if "no such column" in str(e):
|
|
109
|
-
conn.close()
|
|
110
|
-
self._ensure_columns()
|
|
111
|
-
conn = self._get_connection()
|
|
112
|
-
row = conn.execute(
|
|
113
|
-
"SELECT lifecycle_state FROM memories WHERE id = ?",
|
|
114
|
-
(memory_id,),
|
|
115
|
-
).fetchone()
|
|
116
|
-
else:
|
|
117
|
-
raise
|
|
118
|
-
if row is None:
|
|
119
|
-
return None
|
|
120
|
-
return row["lifecycle_state"] or "active"
|
|
121
|
-
finally:
|
|
122
|
-
conn.close()
|
|
123
|
-
|
|
124
|
-
def transition_memory(
|
|
125
|
-
self,
|
|
126
|
-
memory_id: int,
|
|
127
|
-
to_state: str,
|
|
128
|
-
reason: str = "",
|
|
129
|
-
) -> Dict[str, Any]:
|
|
130
|
-
"""Transition a memory to a new lifecycle state.
|
|
131
|
-
|
|
132
|
-
Validates the transition against the state machine, updates the database,
|
|
133
|
-
and appends to the lifecycle_history JSON array.
|
|
134
|
-
|
|
135
|
-
Args:
|
|
136
|
-
memory_id: The memory's database ID
|
|
137
|
-
to_state: Target lifecycle state
|
|
138
|
-
reason: Human-readable reason for the transition
|
|
139
|
-
|
|
140
|
-
Returns:
|
|
141
|
-
Dict with success/failure status, from_state, to_state, etc.
|
|
142
|
-
"""
|
|
143
|
-
with self._lock:
|
|
144
|
-
conn = self._get_connection()
|
|
145
|
-
try:
|
|
146
|
-
row = conn.execute(
|
|
147
|
-
"SELECT lifecycle_state, lifecycle_history FROM memories WHERE id = ?",
|
|
148
|
-
(memory_id,),
|
|
149
|
-
).fetchone()
|
|
150
|
-
|
|
151
|
-
if row is None:
|
|
152
|
-
return {"success": False, "error": f"Memory {memory_id} not found"}
|
|
153
|
-
|
|
154
|
-
from_state = row["lifecycle_state"] or "active"
|
|
155
|
-
|
|
156
|
-
if not self.is_valid_transition(from_state, to_state):
|
|
157
|
-
return {
|
|
158
|
-
"success": False,
|
|
159
|
-
"error": f"Invalid transition from '{from_state}' to '{to_state}'",
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
now = datetime.now().isoformat()
|
|
163
|
-
history = json.loads(row["lifecycle_history"] or "[]")
|
|
164
|
-
history.append({
|
|
165
|
-
"from": from_state,
|
|
166
|
-
"to": to_state,
|
|
167
|
-
"reason": reason,
|
|
168
|
-
"timestamp": now,
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
conn.execute(
|
|
172
|
-
"""UPDATE memories
|
|
173
|
-
SET lifecycle_state = ?,
|
|
174
|
-
lifecycle_updated_at = ?,
|
|
175
|
-
lifecycle_history = ?
|
|
176
|
-
WHERE id = ?""",
|
|
177
|
-
(to_state, now, json.dumps(history), memory_id),
|
|
178
|
-
)
|
|
179
|
-
conn.commit()
|
|
180
|
-
|
|
181
|
-
self._try_emit_event("lifecycle.transitioned", memory_id, {
|
|
182
|
-
"from_state": from_state,
|
|
183
|
-
"to_state": to_state,
|
|
184
|
-
"reason": reason,
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
return {
|
|
188
|
-
"success": True,
|
|
189
|
-
"from_state": from_state,
|
|
190
|
-
"to_state": to_state,
|
|
191
|
-
"memory_id": memory_id,
|
|
192
|
-
"reason": reason,
|
|
193
|
-
"timestamp": now,
|
|
194
|
-
}
|
|
195
|
-
finally:
|
|
196
|
-
conn.close()
|
|
197
|
-
|
|
198
|
-
def batch_transition(
|
|
199
|
-
self,
|
|
200
|
-
memory_ids: List[int],
|
|
201
|
-
to_state: str,
|
|
202
|
-
reasons: Optional[List[str]] = None,
|
|
203
|
-
) -> Dict[str, Any]:
|
|
204
|
-
"""Transition multiple memories in a single connection + commit.
|
|
205
|
-
|
|
206
|
-
Validates each transition individually, skips invalid ones.
|
|
207
|
-
Much faster than calling transition_memory() in a loop because
|
|
208
|
-
it opens only one connection and commits once.
|
|
209
|
-
|
|
210
|
-
Args:
|
|
211
|
-
memory_ids: List of memory IDs to transition
|
|
212
|
-
to_state: Target lifecycle state for all
|
|
213
|
-
reasons: Per-memory reasons (defaults to empty string)
|
|
214
|
-
|
|
215
|
-
Returns:
|
|
216
|
-
Dict with succeeded (list), failed (list), and counts
|
|
217
|
-
"""
|
|
218
|
-
if reasons is None:
|
|
219
|
-
reasons = [""] * len(memory_ids)
|
|
220
|
-
|
|
221
|
-
succeeded: List[Dict[str, Any]] = []
|
|
222
|
-
failed: List[Dict[str, Any]] = []
|
|
223
|
-
|
|
224
|
-
with self._lock:
|
|
225
|
-
conn = self._get_connection()
|
|
226
|
-
try:
|
|
227
|
-
now = datetime.now().isoformat()
|
|
228
|
-
|
|
229
|
-
for mem_id, reason in zip(memory_ids, reasons):
|
|
230
|
-
row = conn.execute(
|
|
231
|
-
"SELECT lifecycle_state, lifecycle_history "
|
|
232
|
-
"FROM memories WHERE id = ?",
|
|
233
|
-
(mem_id,),
|
|
234
|
-
).fetchone()
|
|
235
|
-
|
|
236
|
-
if row is None:
|
|
237
|
-
failed.append({"memory_id": mem_id, "error": "not_found"})
|
|
238
|
-
continue
|
|
239
|
-
|
|
240
|
-
from_state = row["lifecycle_state"] or "active"
|
|
241
|
-
if not self.is_valid_transition(from_state, to_state):
|
|
242
|
-
failed.append({
|
|
243
|
-
"memory_id": mem_id,
|
|
244
|
-
"error": f"invalid_{from_state}_to_{to_state}",
|
|
245
|
-
})
|
|
246
|
-
continue
|
|
247
|
-
|
|
248
|
-
history = json.loads(row["lifecycle_history"] or "[]")
|
|
249
|
-
history.append({
|
|
250
|
-
"from": from_state,
|
|
251
|
-
"to": to_state,
|
|
252
|
-
"reason": reason,
|
|
253
|
-
"timestamp": now,
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
conn.execute(
|
|
257
|
-
"""UPDATE memories
|
|
258
|
-
SET lifecycle_state = ?,
|
|
259
|
-
lifecycle_updated_at = ?,
|
|
260
|
-
lifecycle_history = ?
|
|
261
|
-
WHERE id = ?""",
|
|
262
|
-
(to_state, now, json.dumps(history), mem_id),
|
|
263
|
-
)
|
|
264
|
-
succeeded.append({
|
|
265
|
-
"memory_id": mem_id,
|
|
266
|
-
"from_state": from_state,
|
|
267
|
-
"to_state": to_state,
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
conn.commit()
|
|
271
|
-
|
|
272
|
-
# Best-effort event emission for each transitioned memory
|
|
273
|
-
for entry in succeeded:
|
|
274
|
-
self._try_emit_event(
|
|
275
|
-
"lifecycle.transitioned", entry["memory_id"], {
|
|
276
|
-
"from_state": entry["from_state"],
|
|
277
|
-
"to_state": entry["to_state"],
|
|
278
|
-
"reason": "batch",
|
|
279
|
-
},
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
return {
|
|
283
|
-
"succeeded": succeeded,
|
|
284
|
-
"failed": failed,
|
|
285
|
-
"total": len(memory_ids),
|
|
286
|
-
"success_count": len(succeeded),
|
|
287
|
-
"fail_count": len(failed),
|
|
288
|
-
}
|
|
289
|
-
finally:
|
|
290
|
-
conn.close()
|
|
291
|
-
|
|
292
|
-
def reactivate_memory(
|
|
293
|
-
self,
|
|
294
|
-
memory_id: int,
|
|
295
|
-
trigger: str = "",
|
|
296
|
-
) -> Dict[str, Any]:
|
|
297
|
-
"""Reactivate a non-active memory back to ACTIVE state.
|
|
298
|
-
|
|
299
|
-
Convenience wrapper around transition_memory for reactivation.
|
|
300
|
-
Valid from WARM, COLD, or ARCHIVED states.
|
|
301
|
-
|
|
302
|
-
Args:
|
|
303
|
-
memory_id: The memory's database ID
|
|
304
|
-
trigger: What triggered reactivation (e.g., "recall", "explicit")
|
|
305
|
-
|
|
306
|
-
Returns:
|
|
307
|
-
Dict with success/failure status
|
|
308
|
-
"""
|
|
309
|
-
return self.transition_memory(
|
|
310
|
-
memory_id, "active", reason=f"reactivated:{trigger}"
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
def get_state_distribution(self) -> Dict[str, int]:
|
|
314
|
-
"""Get count of memories in each lifecycle state.
|
|
315
|
-
|
|
316
|
-
Returns:
|
|
317
|
-
Dict mapping state names to counts (all STATES keys present)
|
|
318
|
-
"""
|
|
319
|
-
conn = self._get_connection()
|
|
320
|
-
try:
|
|
321
|
-
dist = {state: 0 for state in self.STATES}
|
|
322
|
-
try:
|
|
323
|
-
rows = conn.execute(
|
|
324
|
-
"SELECT lifecycle_state, COUNT(*) as cnt "
|
|
325
|
-
"FROM memories GROUP BY lifecycle_state"
|
|
326
|
-
).fetchall()
|
|
327
|
-
except sqlite3.OperationalError as e:
|
|
328
|
-
if "no such column" in str(e):
|
|
329
|
-
conn.close()
|
|
330
|
-
self._ensure_columns()
|
|
331
|
-
conn = self._get_connection()
|
|
332
|
-
rows = conn.execute(
|
|
333
|
-
"SELECT lifecycle_state, COUNT(*) as cnt "
|
|
334
|
-
"FROM memories GROUP BY lifecycle_state"
|
|
335
|
-
).fetchall()
|
|
336
|
-
else:
|
|
337
|
-
raise
|
|
338
|
-
for row in rows:
|
|
339
|
-
state = row["lifecycle_state"] if row["lifecycle_state"] else "active"
|
|
340
|
-
if state in dist:
|
|
341
|
-
dist[state] = row["cnt"]
|
|
342
|
-
return dist
|
|
343
|
-
finally:
|
|
344
|
-
conn.close()
|
|
345
|
-
|
|
346
|
-
def _try_emit_event(
|
|
347
|
-
self, event_type: str, memory_id: int, payload: dict
|
|
348
|
-
) -> None:
|
|
349
|
-
"""Best-effort EventBus emission. Fails silently if unavailable."""
|
|
350
|
-
try:
|
|
351
|
-
from event_bus import EventBus
|
|
352
|
-
bus = EventBus.get_instance(Path(self._db_path))
|
|
353
|
-
bus.emit(event_type, payload=payload, memory_id=memory_id)
|
|
354
|
-
except Exception:
|
|
355
|
-
pass
|
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Lifecycle evaluation rules — determines which memories should transition.
|
|
4
|
-
|
|
5
|
-
Evaluates memories against configurable thresholds based on:
|
|
6
|
-
- Time since last access (staleness)
|
|
7
|
-
- Importance score
|
|
8
|
-
- Current lifecycle state
|
|
9
|
-
|
|
10
|
-
Default rules:
|
|
11
|
-
ACTIVE -> WARM: no access >= 30 days AND importance <= 6
|
|
12
|
-
WARM -> COLD: no access >= 90 days AND importance <= 4
|
|
13
|
-
COLD -> ARCHIVED: no access >= 180 days (any importance)
|
|
14
|
-
|
|
15
|
-
Thresholds configurable via lifecycle_config.json.
|
|
16
|
-
"""
|
|
17
|
-
import sqlite3
|
|
18
|
-
import json
|
|
19
|
-
from datetime import datetime
|
|
20
|
-
from pathlib import Path
|
|
21
|
-
from typing import Optional, Dict, Any, List, Set
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
# Default evaluation thresholds
|
|
25
|
-
DEFAULT_EVAL_CONFIG: Dict[str, Dict[str, Any]] = {
|
|
26
|
-
"active_to_warm": {
|
|
27
|
-
"no_access_days": 30,
|
|
28
|
-
"max_importance": 6,
|
|
29
|
-
},
|
|
30
|
-
"warm_to_cold": {
|
|
31
|
-
"no_access_days": 90,
|
|
32
|
-
"max_importance": 4,
|
|
33
|
-
},
|
|
34
|
-
"cold_to_archived": {
|
|
35
|
-
"no_access_days": 180,
|
|
36
|
-
},
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class LifecycleEvaluator:
|
|
41
|
-
"""Evaluates memories for lifecycle state transitions.
|
|
42
|
-
|
|
43
|
-
Scans memories and recommends transitions based on staleness and importance.
|
|
44
|
-
Does NOT execute transitions — returns recommendations for the engine or
|
|
45
|
-
scheduler to act on.
|
|
46
|
-
"""
|
|
47
|
-
|
|
48
|
-
def __init__(
|
|
49
|
-
self, db_path: Optional[str] = None, config_path: Optional[str] = None
|
|
50
|
-
):
|
|
51
|
-
if db_path is None:
|
|
52
|
-
db_path = str(Path.home() / ".claude-memory" / "memory.db")
|
|
53
|
-
self._db_path = str(db_path)
|
|
54
|
-
self._config_path = config_path
|
|
55
|
-
|
|
56
|
-
def _get_connection(self) -> sqlite3.Connection:
|
|
57
|
-
"""Get a SQLite connection to memory.db."""
|
|
58
|
-
conn = sqlite3.connect(self._db_path)
|
|
59
|
-
conn.row_factory = sqlite3.Row
|
|
60
|
-
return conn
|
|
61
|
-
|
|
62
|
-
def _ensure_lifecycle_columns(self) -> None:
|
|
63
|
-
"""Ensure v2.8 lifecycle columns exist via LifecycleEngine."""
|
|
64
|
-
try:
|
|
65
|
-
from lifecycle.lifecycle_engine import LifecycleEngine
|
|
66
|
-
engine = LifecycleEngine(db_path=self._db_path)
|
|
67
|
-
engine._ensure_columns()
|
|
68
|
-
except Exception:
|
|
69
|
-
pass # Best effort — don't block evaluation
|
|
70
|
-
|
|
71
|
-
def evaluate_memories(
|
|
72
|
-
self,
|
|
73
|
-
profile: Optional[str] = None,
|
|
74
|
-
retention_overrides: Optional[Set[int]] = None,
|
|
75
|
-
) -> List[Dict[str, Any]]:
|
|
76
|
-
"""Scan all memories and return recommended transitions.
|
|
77
|
-
|
|
78
|
-
Args:
|
|
79
|
-
profile: Filter by profile (None = all profiles)
|
|
80
|
-
retention_overrides: Set of memory IDs to skip (retention-protected)
|
|
81
|
-
|
|
82
|
-
Returns:
|
|
83
|
-
List of recommendation dicts with memory_id, from_state, to_state, reason
|
|
84
|
-
"""
|
|
85
|
-
config = self._load_config()
|
|
86
|
-
overrides = retention_overrides or set()
|
|
87
|
-
|
|
88
|
-
conn = self._get_connection()
|
|
89
|
-
try:
|
|
90
|
-
query = (
|
|
91
|
-
"SELECT id, lifecycle_state, importance, last_accessed, created_at "
|
|
92
|
-
"FROM memories WHERE lifecycle_state IN ('active', 'warm', 'cold')"
|
|
93
|
-
)
|
|
94
|
-
params: list = []
|
|
95
|
-
if profile:
|
|
96
|
-
query += " AND profile = ?"
|
|
97
|
-
params.append(profile)
|
|
98
|
-
|
|
99
|
-
try:
|
|
100
|
-
rows = conn.execute(query, params).fetchall()
|
|
101
|
-
except sqlite3.OperationalError as e:
|
|
102
|
-
if "no such column" in str(e):
|
|
103
|
-
conn.close()
|
|
104
|
-
self._ensure_lifecycle_columns()
|
|
105
|
-
conn = self._get_connection()
|
|
106
|
-
rows = conn.execute(query, params).fetchall()
|
|
107
|
-
else:
|
|
108
|
-
raise
|
|
109
|
-
|
|
110
|
-
recommendations = []
|
|
111
|
-
now = datetime.now()
|
|
112
|
-
|
|
113
|
-
for row in rows:
|
|
114
|
-
if row["id"] in overrides:
|
|
115
|
-
continue
|
|
116
|
-
rec = self._evaluate_row(row, config, now)
|
|
117
|
-
if rec:
|
|
118
|
-
recommendations.append(rec)
|
|
119
|
-
|
|
120
|
-
return recommendations
|
|
121
|
-
finally:
|
|
122
|
-
conn.close()
|
|
123
|
-
|
|
124
|
-
def evaluate_single(
|
|
125
|
-
self,
|
|
126
|
-
memory_id: int,
|
|
127
|
-
retention_overrides: Optional[Set[int]] = None,
|
|
128
|
-
) -> Optional[Dict[str, Any]]:
|
|
129
|
-
"""Evaluate a single memory for potential transition.
|
|
130
|
-
|
|
131
|
-
Args:
|
|
132
|
-
memory_id: The memory's database ID
|
|
133
|
-
retention_overrides: Set of memory IDs to skip
|
|
134
|
-
|
|
135
|
-
Returns:
|
|
136
|
-
Recommendation dict, or None if no transition recommended
|
|
137
|
-
"""
|
|
138
|
-
overrides = retention_overrides or set()
|
|
139
|
-
if memory_id in overrides:
|
|
140
|
-
return None
|
|
141
|
-
|
|
142
|
-
config = self._load_config()
|
|
143
|
-
conn = self._get_connection()
|
|
144
|
-
try:
|
|
145
|
-
try:
|
|
146
|
-
row = conn.execute(
|
|
147
|
-
"SELECT id, lifecycle_state, importance, last_accessed, created_at "
|
|
148
|
-
"FROM memories WHERE id = ?",
|
|
149
|
-
(memory_id,),
|
|
150
|
-
).fetchone()
|
|
151
|
-
except sqlite3.OperationalError as e:
|
|
152
|
-
if "no such column" in str(e):
|
|
153
|
-
conn.close()
|
|
154
|
-
self._ensure_lifecycle_columns()
|
|
155
|
-
conn = self._get_connection()
|
|
156
|
-
row = conn.execute(
|
|
157
|
-
"SELECT id, lifecycle_state, importance, last_accessed, created_at "
|
|
158
|
-
"FROM memories WHERE id = ?",
|
|
159
|
-
(memory_id,),
|
|
160
|
-
).fetchone()
|
|
161
|
-
else:
|
|
162
|
-
raise
|
|
163
|
-
if row is None:
|
|
164
|
-
return None
|
|
165
|
-
return self._evaluate_row(row, config, datetime.now())
|
|
166
|
-
finally:
|
|
167
|
-
conn.close()
|
|
168
|
-
|
|
169
|
-
def _evaluate_row(
|
|
170
|
-
self, row: sqlite3.Row, config: Dict, now: datetime
|
|
171
|
-
) -> Optional[Dict[str, Any]]:
|
|
172
|
-
"""Evaluate a single memory row against transition rules."""
|
|
173
|
-
state = row["lifecycle_state"] or "active"
|
|
174
|
-
importance = row["importance"] or 5
|
|
175
|
-
|
|
176
|
-
# Determine staleness: prefer last_accessed, fall back to created_at
|
|
177
|
-
last_access_str = row["last_accessed"] or row["created_at"]
|
|
178
|
-
if last_access_str:
|
|
179
|
-
try:
|
|
180
|
-
last_access = datetime.fromisoformat(str(last_access_str))
|
|
181
|
-
except (ValueError, TypeError):
|
|
182
|
-
last_access = now # Unparseable -> treat as recent (safe default)
|
|
183
|
-
else:
|
|
184
|
-
last_access = now
|
|
185
|
-
|
|
186
|
-
days_stale = (now - last_access).days
|
|
187
|
-
|
|
188
|
-
if state == "active":
|
|
189
|
-
rules = config.get("active_to_warm", {})
|
|
190
|
-
threshold_days = rules.get("no_access_days", 30)
|
|
191
|
-
max_importance = rules.get("max_importance", 6)
|
|
192
|
-
if days_stale >= threshold_days and importance <= max_importance:
|
|
193
|
-
return self._build_recommendation(
|
|
194
|
-
row["id"], "active", "warm", days_stale, importance
|
|
195
|
-
)
|
|
196
|
-
elif state == "warm":
|
|
197
|
-
rules = config.get("warm_to_cold", {})
|
|
198
|
-
threshold_days = rules.get("no_access_days", 90)
|
|
199
|
-
max_importance = rules.get("max_importance", 4)
|
|
200
|
-
if days_stale >= threshold_days and importance <= max_importance:
|
|
201
|
-
return self._build_recommendation(
|
|
202
|
-
row["id"], "warm", "cold", days_stale, importance
|
|
203
|
-
)
|
|
204
|
-
elif state == "cold":
|
|
205
|
-
rules = config.get("cold_to_archived", {})
|
|
206
|
-
threshold_days = rules.get("no_access_days", 180)
|
|
207
|
-
if days_stale >= threshold_days:
|
|
208
|
-
return self._build_recommendation(
|
|
209
|
-
row["id"], "cold", "archived", days_stale, importance
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
return None
|
|
213
|
-
|
|
214
|
-
def _build_recommendation(
|
|
215
|
-
self,
|
|
216
|
-
memory_id: int,
|
|
217
|
-
from_state: str,
|
|
218
|
-
to_state: str,
|
|
219
|
-
days_stale: int,
|
|
220
|
-
importance: int,
|
|
221
|
-
) -> Dict[str, Any]:
|
|
222
|
-
"""Build a standardized recommendation dict."""
|
|
223
|
-
reason = f"no_access_{days_stale}d"
|
|
224
|
-
if to_state != "archived":
|
|
225
|
-
reason += f"_importance_{importance}"
|
|
226
|
-
return {
|
|
227
|
-
"memory_id": memory_id,
|
|
228
|
-
"from_state": from_state,
|
|
229
|
-
"to_state": to_state,
|
|
230
|
-
"reason": reason,
|
|
231
|
-
"days_stale": days_stale,
|
|
232
|
-
"importance": importance,
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
def _load_config(self) -> Dict[str, Any]:
|
|
236
|
-
"""Load lifecycle evaluation config from JSON. Returns defaults if missing."""
|
|
237
|
-
try:
|
|
238
|
-
if self._config_path:
|
|
239
|
-
config_path = Path(self._config_path)
|
|
240
|
-
else:
|
|
241
|
-
config_path = Path(self._db_path).parent / "lifecycle_config.json"
|
|
242
|
-
if config_path.exists():
|
|
243
|
-
with open(config_path) as f:
|
|
244
|
-
user_config = json.load(f)
|
|
245
|
-
merged: Dict[str, Any] = {}
|
|
246
|
-
for key in DEFAULT_EVAL_CONFIG:
|
|
247
|
-
if key in user_config and isinstance(user_config[key], dict):
|
|
248
|
-
merged[key] = {**DEFAULT_EVAL_CONFIG[key], **user_config[key]}
|
|
249
|
-
else:
|
|
250
|
-
merged[key] = dict(DEFAULT_EVAL_CONFIG[key])
|
|
251
|
-
for key in user_config:
|
|
252
|
-
if key not in merged:
|
|
253
|
-
merged[key] = user_config[key]
|
|
254
|
-
return merged
|
|
255
|
-
except Exception:
|
|
256
|
-
pass
|
|
257
|
-
return {k: dict(v) for k, v in DEFAULT_EVAL_CONFIG.items()}
|