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,179 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Tests for compaction engine — content archival and restoration.
|
|
4
|
-
"""
|
|
5
|
-
import sqlite3
|
|
6
|
-
import tempfile
|
|
7
|
-
import os
|
|
8
|
-
import sys
|
|
9
|
-
import json
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
|
|
12
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class TestCompactionEngine:
|
|
16
|
-
"""Test memory compaction and restoration."""
|
|
17
|
-
|
|
18
|
-
def setup_method(self):
|
|
19
|
-
self.tmp_dir = tempfile.mkdtemp()
|
|
20
|
-
self.db_path = os.path.join(self.tmp_dir, "test.db")
|
|
21
|
-
conn = sqlite3.connect(self.db_path)
|
|
22
|
-
conn.execute("""
|
|
23
|
-
CREATE TABLE memories (
|
|
24
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
25
|
-
content TEXT NOT NULL,
|
|
26
|
-
importance INTEGER DEFAULT 5,
|
|
27
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
28
|
-
last_accessed TIMESTAMP,
|
|
29
|
-
access_count INTEGER DEFAULT 0,
|
|
30
|
-
lifecycle_state TEXT DEFAULT 'active',
|
|
31
|
-
lifecycle_updated_at TIMESTAMP,
|
|
32
|
-
lifecycle_history TEXT DEFAULT '[]',
|
|
33
|
-
access_level TEXT DEFAULT 'public',
|
|
34
|
-
profile TEXT DEFAULT 'default',
|
|
35
|
-
tags TEXT DEFAULT '[]',
|
|
36
|
-
summary TEXT
|
|
37
|
-
)
|
|
38
|
-
""")
|
|
39
|
-
conn.execute("""
|
|
40
|
-
CREATE TABLE IF NOT EXISTS memory_archive (
|
|
41
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
42
|
-
memory_id INTEGER UNIQUE NOT NULL,
|
|
43
|
-
full_content TEXT NOT NULL,
|
|
44
|
-
archived_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
45
|
-
FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE
|
|
46
|
-
)
|
|
47
|
-
""")
|
|
48
|
-
conn.execute("CREATE INDEX IF NOT EXISTS idx_archive_memory ON memory_archive(memory_id)")
|
|
49
|
-
|
|
50
|
-
# Memory 1: Long content suitable for compaction
|
|
51
|
-
long_content = (
|
|
52
|
-
"The Python programming language is widely used for machine learning and data science. "
|
|
53
|
-
"It provides libraries like scikit-learn, TensorFlow, and PyTorch for building models. "
|
|
54
|
-
"Python's simplicity and readability make it ideal for rapid prototyping. "
|
|
55
|
-
"The ecosystem includes tools for data preprocessing, visualization, and deployment. "
|
|
56
|
-
"Many enterprise applications use Python for backend services and API development."
|
|
57
|
-
)
|
|
58
|
-
conn.execute(
|
|
59
|
-
"INSERT INTO memories (content, importance, lifecycle_state, tags) VALUES (?, ?, ?, ?)",
|
|
60
|
-
(long_content, 5, "cold", '["python","ml"]'),
|
|
61
|
-
)
|
|
62
|
-
# Memory 2: Short content
|
|
63
|
-
conn.execute(
|
|
64
|
-
"INSERT INTO memories (content, importance, lifecycle_state) VALUES (?, ?, ?)",
|
|
65
|
-
("brief note about testing", 3, "cold"),
|
|
66
|
-
)
|
|
67
|
-
# Memory 3: Already archived
|
|
68
|
-
conn.execute(
|
|
69
|
-
"INSERT INTO memories (content, importance, lifecycle_state) VALUES (?, ?, ?)",
|
|
70
|
-
("[COMPACTED] Key entities: database, SQL", 5, "archived"),
|
|
71
|
-
)
|
|
72
|
-
conn.execute(
|
|
73
|
-
"INSERT INTO memory_archive (memory_id, full_content) VALUES (?, ?)",
|
|
74
|
-
(3, "The database management system uses SQL for querying and PostgreSQL for storage."),
|
|
75
|
-
)
|
|
76
|
-
conn.commit()
|
|
77
|
-
conn.close()
|
|
78
|
-
|
|
79
|
-
def teardown_method(self):
|
|
80
|
-
import shutil
|
|
81
|
-
shutil.rmtree(self.tmp_dir, ignore_errors=True)
|
|
82
|
-
|
|
83
|
-
def test_compact_memory_archives_content(self):
|
|
84
|
-
"""Compaction stores full content in memory_archive."""
|
|
85
|
-
from lifecycle.compaction_engine import CompactionEngine
|
|
86
|
-
engine = CompactionEngine(self.db_path)
|
|
87
|
-
result = engine.compact_memory(1)
|
|
88
|
-
assert result["success"] is True
|
|
89
|
-
# Verify archive has full content
|
|
90
|
-
conn = sqlite3.connect(self.db_path)
|
|
91
|
-
row = conn.execute("SELECT full_content FROM memory_archive WHERE memory_id=1").fetchone()
|
|
92
|
-
conn.close()
|
|
93
|
-
assert row is not None
|
|
94
|
-
assert "Python programming" in row[0]
|
|
95
|
-
|
|
96
|
-
def test_compact_memory_replaces_content(self):
|
|
97
|
-
"""Compacted memory content is replaced with summary + entities."""
|
|
98
|
-
from lifecycle.compaction_engine import CompactionEngine
|
|
99
|
-
engine = CompactionEngine(self.db_path)
|
|
100
|
-
engine.compact_memory(1)
|
|
101
|
-
conn = sqlite3.connect(self.db_path)
|
|
102
|
-
row = conn.execute("SELECT content FROM memories WHERE id=1").fetchone()
|
|
103
|
-
conn.close()
|
|
104
|
-
# Content should be shorter than original
|
|
105
|
-
assert len(row[0]) < 300
|
|
106
|
-
assert "[COMPACTED]" in row[0]
|
|
107
|
-
|
|
108
|
-
def test_compact_preserves_key_entities(self):
|
|
109
|
-
"""Compacted content preserves key entities/terms."""
|
|
110
|
-
from lifecycle.compaction_engine import CompactionEngine
|
|
111
|
-
engine = CompactionEngine(self.db_path)
|
|
112
|
-
result = engine.compact_memory(1)
|
|
113
|
-
assert "entities" in result
|
|
114
|
-
assert len(result["entities"]) >= 3
|
|
115
|
-
# Should extract key terms like "python", "learning", "data"
|
|
116
|
-
entities_lower = [e.lower() for e in result["entities"]]
|
|
117
|
-
assert any("python" in e for e in entities_lower)
|
|
118
|
-
|
|
119
|
-
def test_compact_preserves_tags(self):
|
|
120
|
-
"""Compaction does NOT remove tags from the memory."""
|
|
121
|
-
from lifecycle.compaction_engine import CompactionEngine
|
|
122
|
-
engine = CompactionEngine(self.db_path)
|
|
123
|
-
engine.compact_memory(1)
|
|
124
|
-
conn = sqlite3.connect(self.db_path)
|
|
125
|
-
row = conn.execute("SELECT tags FROM memories WHERE id=1").fetchone()
|
|
126
|
-
conn.close()
|
|
127
|
-
assert row[0] is not None
|
|
128
|
-
tags = json.loads(row[0])
|
|
129
|
-
assert "python" in tags
|
|
130
|
-
|
|
131
|
-
def test_restore_memory_from_archive(self):
|
|
132
|
-
"""Restoring a compacted memory brings back full content."""
|
|
133
|
-
from lifecycle.compaction_engine import CompactionEngine
|
|
134
|
-
engine = CompactionEngine(self.db_path)
|
|
135
|
-
result = engine.restore_memory(3) # Already archived memory
|
|
136
|
-
assert result["success"] is True
|
|
137
|
-
conn = sqlite3.connect(self.db_path)
|
|
138
|
-
row = conn.execute("SELECT content FROM memories WHERE id=3").fetchone()
|
|
139
|
-
conn.close()
|
|
140
|
-
assert "database management" in row[0]
|
|
141
|
-
|
|
142
|
-
def test_restore_cleans_archive(self):
|
|
143
|
-
"""After restoration, the archive entry is removed."""
|
|
144
|
-
from lifecycle.compaction_engine import CompactionEngine
|
|
145
|
-
engine = CompactionEngine(self.db_path)
|
|
146
|
-
engine.restore_memory(3)
|
|
147
|
-
conn = sqlite3.connect(self.db_path)
|
|
148
|
-
row = conn.execute("SELECT * FROM memory_archive WHERE memory_id=3").fetchone()
|
|
149
|
-
conn.close()
|
|
150
|
-
assert row is None
|
|
151
|
-
|
|
152
|
-
def test_dry_run_no_changes(self):
|
|
153
|
-
"""dry_run mode shows what would happen without modifying DB."""
|
|
154
|
-
from lifecycle.compaction_engine import CompactionEngine
|
|
155
|
-
engine = CompactionEngine(self.db_path)
|
|
156
|
-
result = engine.compact_memory(1, dry_run=True)
|
|
157
|
-
assert result["success"] is True
|
|
158
|
-
assert result["dry_run"] is True
|
|
159
|
-
# Verify DB was NOT modified
|
|
160
|
-
conn = sqlite3.connect(self.db_path)
|
|
161
|
-
row = conn.execute("SELECT content FROM memories WHERE id=1").fetchone()
|
|
162
|
-
archive = conn.execute("SELECT * FROM memory_archive WHERE memory_id=1").fetchone()
|
|
163
|
-
conn.close()
|
|
164
|
-
assert "Python programming" in row[0] # Original content still there
|
|
165
|
-
assert archive is None # No archive entry created
|
|
166
|
-
|
|
167
|
-
def test_compact_nonexistent_memory(self):
|
|
168
|
-
"""Compacting nonexistent memory returns failure."""
|
|
169
|
-
from lifecycle.compaction_engine import CompactionEngine
|
|
170
|
-
engine = CompactionEngine(self.db_path)
|
|
171
|
-
result = engine.compact_memory(999)
|
|
172
|
-
assert result["success"] is False
|
|
173
|
-
|
|
174
|
-
def test_restore_nonexistent_archive(self):
|
|
175
|
-
"""Restoring memory without archive entry returns failure."""
|
|
176
|
-
from lifecycle.compaction_engine import CompactionEngine
|
|
177
|
-
engine = CompactionEngine(self.db_path)
|
|
178
|
-
result = engine.restore_memory(1) # Memory 1 has no archive
|
|
179
|
-
assert result["success"] is False
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Tests for lifecycle state machine transitions.
|
|
4
|
-
"""
|
|
5
|
-
import sqlite3
|
|
6
|
-
import tempfile
|
|
7
|
-
import os
|
|
8
|
-
import sys
|
|
9
|
-
import json
|
|
10
|
-
import pytest
|
|
11
|
-
|
|
12
|
-
# Ensure src/ is importable and takes precedence (matches existing test pattern)
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
SRC_DIR = Path(__file__).resolve().parent.parent.parent # src/
|
|
15
|
-
_src_str = str(SRC_DIR)
|
|
16
|
-
if _src_str not in sys.path:
|
|
17
|
-
sys.path.insert(0, _src_str)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class TestLifecycleStates:
|
|
21
|
-
"""Test state definitions and valid transitions."""
|
|
22
|
-
|
|
23
|
-
def setup_method(self):
|
|
24
|
-
self.db_fd, self.db_path = tempfile.mkstemp(suffix=".db")
|
|
25
|
-
conn = sqlite3.connect(self.db_path)
|
|
26
|
-
conn.execute("""
|
|
27
|
-
CREATE TABLE memories (
|
|
28
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
29
|
-
content TEXT NOT NULL,
|
|
30
|
-
importance INTEGER DEFAULT 5,
|
|
31
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
32
|
-
last_accessed TIMESTAMP,
|
|
33
|
-
access_count INTEGER DEFAULT 0,
|
|
34
|
-
lifecycle_state TEXT DEFAULT 'active',
|
|
35
|
-
lifecycle_updated_at TIMESTAMP,
|
|
36
|
-
lifecycle_history TEXT DEFAULT '[]',
|
|
37
|
-
access_level TEXT DEFAULT 'public',
|
|
38
|
-
profile TEXT DEFAULT 'default'
|
|
39
|
-
)
|
|
40
|
-
""")
|
|
41
|
-
conn.execute("""
|
|
42
|
-
INSERT INTO memories (content, importance, lifecycle_state)
|
|
43
|
-
VALUES ('test memory', 5, 'active')
|
|
44
|
-
""")
|
|
45
|
-
conn.commit()
|
|
46
|
-
conn.close()
|
|
47
|
-
|
|
48
|
-
def teardown_method(self):
|
|
49
|
-
os.close(self.db_fd)
|
|
50
|
-
os.unlink(self.db_path)
|
|
51
|
-
|
|
52
|
-
def test_valid_states(self):
|
|
53
|
-
from lifecycle.lifecycle_engine import LifecycleEngine
|
|
54
|
-
engine = LifecycleEngine(self.db_path)
|
|
55
|
-
assert set(engine.STATES) == {"active", "warm", "cold", "archived", "tombstoned"}
|
|
56
|
-
|
|
57
|
-
def test_valid_transition_active_to_warm(self):
|
|
58
|
-
from lifecycle.lifecycle_engine import LifecycleEngine
|
|
59
|
-
engine = LifecycleEngine(self.db_path)
|
|
60
|
-
assert engine.is_valid_transition("active", "warm") is True
|
|
61
|
-
|
|
62
|
-
def test_invalid_transition_active_to_archived(self):
|
|
63
|
-
from lifecycle.lifecycle_engine import LifecycleEngine
|
|
64
|
-
engine = LifecycleEngine(self.db_path)
|
|
65
|
-
assert engine.is_valid_transition("active", "archived") is False
|
|
66
|
-
|
|
67
|
-
def test_reactivation_always_valid(self):
|
|
68
|
-
from lifecycle.lifecycle_engine import LifecycleEngine
|
|
69
|
-
engine = LifecycleEngine(self.db_path)
|
|
70
|
-
for state in ["warm", "cold", "archived"]:
|
|
71
|
-
assert engine.is_valid_transition(state, "active") is True
|
|
72
|
-
|
|
73
|
-
def test_tombstoned_is_terminal(self):
|
|
74
|
-
from lifecycle.lifecycle_engine import LifecycleEngine
|
|
75
|
-
engine = LifecycleEngine(self.db_path)
|
|
76
|
-
for state in engine.STATES:
|
|
77
|
-
if state != "tombstoned":
|
|
78
|
-
assert engine.is_valid_transition("tombstoned", state) is False
|
|
79
|
-
|
|
80
|
-
def test_transition_memory(self):
|
|
81
|
-
from lifecycle.lifecycle_engine import LifecycleEngine
|
|
82
|
-
engine = LifecycleEngine(self.db_path)
|
|
83
|
-
result = engine.transition_memory(1, "warm", reason="no_access_30d")
|
|
84
|
-
assert result["success"] is True
|
|
85
|
-
assert result["from_state"] == "active"
|
|
86
|
-
assert result["to_state"] == "warm"
|
|
87
|
-
|
|
88
|
-
def test_transition_updates_db(self):
|
|
89
|
-
from lifecycle.lifecycle_engine import LifecycleEngine
|
|
90
|
-
engine = LifecycleEngine(self.db_path)
|
|
91
|
-
engine.transition_memory(1, "warm", reason="no_access_30d")
|
|
92
|
-
conn = sqlite3.connect(self.db_path)
|
|
93
|
-
row = conn.execute("SELECT lifecycle_state FROM memories WHERE id=1").fetchone()
|
|
94
|
-
conn.close()
|
|
95
|
-
assert row[0] == "warm"
|
|
96
|
-
|
|
97
|
-
def test_transition_records_history(self):
|
|
98
|
-
from lifecycle.lifecycle_engine import LifecycleEngine
|
|
99
|
-
engine = LifecycleEngine(self.db_path)
|
|
100
|
-
engine.transition_memory(1, "warm", reason="no_access_30d")
|
|
101
|
-
conn = sqlite3.connect(self.db_path)
|
|
102
|
-
row = conn.execute("SELECT lifecycle_history FROM memories WHERE id=1").fetchone()
|
|
103
|
-
conn.close()
|
|
104
|
-
history = json.loads(row[0])
|
|
105
|
-
assert len(history) == 1
|
|
106
|
-
assert history[0]["from"] == "active"
|
|
107
|
-
assert history[0]["to"] == "warm"
|
|
108
|
-
assert history[0]["reason"] == "no_access_30d"
|
|
109
|
-
|
|
110
|
-
def test_invalid_transition_rejected(self):
|
|
111
|
-
from lifecycle.lifecycle_engine import LifecycleEngine
|
|
112
|
-
engine = LifecycleEngine(self.db_path)
|
|
113
|
-
result = engine.transition_memory(1, "archived", reason="skip")
|
|
114
|
-
assert result["success"] is False
|
|
115
|
-
assert "invalid" in result["error"].lower()
|
|
116
|
-
|
|
117
|
-
def test_get_memory_state(self):
|
|
118
|
-
from lifecycle.lifecycle_engine import LifecycleEngine
|
|
119
|
-
engine = LifecycleEngine(self.db_path)
|
|
120
|
-
state = engine.get_memory_state(1)
|
|
121
|
-
assert state == "active"
|
|
122
|
-
|
|
123
|
-
def test_get_state_distribution(self):
|
|
124
|
-
from lifecycle.lifecycle_engine import LifecycleEngine
|
|
125
|
-
engine = LifecycleEngine(self.db_path)
|
|
126
|
-
dist = engine.get_state_distribution()
|
|
127
|
-
assert dist["active"] >= 1
|
|
128
|
-
assert dist["warm"] == 0
|
|
129
|
-
|
|
130
|
-
def test_reactivation_on_access(self):
|
|
131
|
-
from lifecycle.lifecycle_engine import LifecycleEngine
|
|
132
|
-
engine = LifecycleEngine(self.db_path)
|
|
133
|
-
engine.transition_memory(1, "warm", reason="aged")
|
|
134
|
-
result = engine.reactivate_memory(1, trigger="recall")
|
|
135
|
-
assert result["success"] is True
|
|
136
|
-
assert result["from_state"] == "warm"
|
|
137
|
-
assert result["to_state"] == "active"
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Tests for lifecycle evaluation rules — which memories should transition.
|
|
4
|
-
"""
|
|
5
|
-
import sqlite3
|
|
6
|
-
import tempfile
|
|
7
|
-
import os
|
|
8
|
-
import sys
|
|
9
|
-
import json
|
|
10
|
-
from datetime import datetime, timedelta
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
|
|
13
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class TestLifecycleEvaluation:
|
|
17
|
-
"""Test evaluation rules for memory lifecycle transitions."""
|
|
18
|
-
|
|
19
|
-
def setup_method(self):
|
|
20
|
-
# Create temp dir for DB + config isolation
|
|
21
|
-
self.tmp_dir = tempfile.mkdtemp()
|
|
22
|
-
self.db_path = os.path.join(self.tmp_dir, "test.db")
|
|
23
|
-
conn = sqlite3.connect(self.db_path)
|
|
24
|
-
conn.execute("""
|
|
25
|
-
CREATE TABLE memories (
|
|
26
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
27
|
-
content TEXT NOT NULL,
|
|
28
|
-
importance INTEGER DEFAULT 5,
|
|
29
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
30
|
-
last_accessed TIMESTAMP,
|
|
31
|
-
access_count INTEGER DEFAULT 0,
|
|
32
|
-
lifecycle_state TEXT DEFAULT 'active',
|
|
33
|
-
lifecycle_updated_at TIMESTAMP,
|
|
34
|
-
lifecycle_history TEXT DEFAULT '[]',
|
|
35
|
-
access_level TEXT DEFAULT 'public',
|
|
36
|
-
profile TEXT DEFAULT 'default'
|
|
37
|
-
)
|
|
38
|
-
""")
|
|
39
|
-
now = datetime.now()
|
|
40
|
-
|
|
41
|
-
# Memory 1: Active, stale (35d), low importance (5) → should recommend WARM
|
|
42
|
-
conn.execute(
|
|
43
|
-
"INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at) VALUES (?, ?, ?, ?, ?)",
|
|
44
|
-
("stale low importance", 5, "active", (now - timedelta(days=35)).isoformat(), (now - timedelta(days=100)).isoformat()),
|
|
45
|
-
)
|
|
46
|
-
# Memory 2: Active, recent (10d), low importance (5) → should STAY
|
|
47
|
-
conn.execute(
|
|
48
|
-
"INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at) VALUES (?, ?, ?, ?, ?)",
|
|
49
|
-
("recent access", 5, "active", (now - timedelta(days=10)).isoformat(), (now - timedelta(days=100)).isoformat()),
|
|
50
|
-
)
|
|
51
|
-
# Memory 3: Active, stale (35d), HIGH importance (8) → should STAY (importance resists)
|
|
52
|
-
conn.execute(
|
|
53
|
-
"INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at) VALUES (?, ?, ?, ?, ?)",
|
|
54
|
-
("stale high importance", 8, "active", (now - timedelta(days=35)).isoformat(), (now - timedelta(days=100)).isoformat()),
|
|
55
|
-
)
|
|
56
|
-
# Memory 4: Warm, stale (95d), low importance (3) → should recommend COLD
|
|
57
|
-
conn.execute(
|
|
58
|
-
"INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at) VALUES (?, ?, ?, ?, ?)",
|
|
59
|
-
("warm stale", 3, "warm", (now - timedelta(days=95)).isoformat(), (now - timedelta(days=200)).isoformat()),
|
|
60
|
-
)
|
|
61
|
-
# Memory 5: Cold, very stale (200d), importance 5 → should recommend ARCHIVED
|
|
62
|
-
conn.execute(
|
|
63
|
-
"INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at) VALUES (?, ?, ?, ?, ?)",
|
|
64
|
-
("cold very stale", 5, "cold", (now - timedelta(days=200)).isoformat(), (now - timedelta(days=300)).isoformat()),
|
|
65
|
-
)
|
|
66
|
-
# Memory 6: Active, NULL last_accessed, created 40d ago, importance 4 → WARM (uses created_at)
|
|
67
|
-
conn.execute(
|
|
68
|
-
"INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at) VALUES (?, ?, ?, ?, ?)",
|
|
69
|
-
("never accessed", 4, "active", None, (now - timedelta(days=40)).isoformat()),
|
|
70
|
-
)
|
|
71
|
-
conn.commit()
|
|
72
|
-
conn.close()
|
|
73
|
-
|
|
74
|
-
def teardown_method(self):
|
|
75
|
-
import shutil
|
|
76
|
-
shutil.rmtree(self.tmp_dir, ignore_errors=True)
|
|
77
|
-
|
|
78
|
-
def test_active_to_warm_stale_low_importance(self):
|
|
79
|
-
"""Memory 1: stale 35d, importance 5 → recommend ACTIVE→WARM."""
|
|
80
|
-
from lifecycle.lifecycle_evaluator import LifecycleEvaluator
|
|
81
|
-
evaluator = LifecycleEvaluator(self.db_path)
|
|
82
|
-
rec = evaluator.evaluate_single(1)
|
|
83
|
-
assert rec is not None
|
|
84
|
-
assert rec["from_state"] == "active"
|
|
85
|
-
assert rec["to_state"] == "warm"
|
|
86
|
-
assert rec["memory_id"] == 1
|
|
87
|
-
|
|
88
|
-
def test_active_stays_recent_access(self):
|
|
89
|
-
"""Memory 2: accessed 10d ago → no transition recommended."""
|
|
90
|
-
from lifecycle.lifecycle_evaluator import LifecycleEvaluator
|
|
91
|
-
evaluator = LifecycleEvaluator(self.db_path)
|
|
92
|
-
rec = evaluator.evaluate_single(2)
|
|
93
|
-
assert rec is None
|
|
94
|
-
|
|
95
|
-
def test_active_stays_high_importance(self):
|
|
96
|
-
"""Memory 3: importance 8 resists transition even when stale."""
|
|
97
|
-
from lifecycle.lifecycle_evaluator import LifecycleEvaluator
|
|
98
|
-
evaluator = LifecycleEvaluator(self.db_path)
|
|
99
|
-
rec = evaluator.evaluate_single(3)
|
|
100
|
-
assert rec is None
|
|
101
|
-
|
|
102
|
-
def test_warm_to_cold_stale(self):
|
|
103
|
-
"""Memory 4: warm, stale 95d, importance 3 → recommend COLD."""
|
|
104
|
-
from lifecycle.lifecycle_evaluator import LifecycleEvaluator
|
|
105
|
-
evaluator = LifecycleEvaluator(self.db_path)
|
|
106
|
-
rec = evaluator.evaluate_single(4)
|
|
107
|
-
assert rec is not None
|
|
108
|
-
assert rec["from_state"] == "warm"
|
|
109
|
-
assert rec["to_state"] == "cold"
|
|
110
|
-
|
|
111
|
-
def test_cold_to_archived(self):
|
|
112
|
-
"""Memory 5: cold, stale 200d → recommend ARCHIVED."""
|
|
113
|
-
from lifecycle.lifecycle_evaluator import LifecycleEvaluator
|
|
114
|
-
evaluator = LifecycleEvaluator(self.db_path)
|
|
115
|
-
rec = evaluator.evaluate_single(5)
|
|
116
|
-
assert rec is not None
|
|
117
|
-
assert rec["from_state"] == "cold"
|
|
118
|
-
assert rec["to_state"] == "archived"
|
|
119
|
-
|
|
120
|
-
def test_never_accessed_uses_created_at(self):
|
|
121
|
-
"""Memory 6: NULL last_accessed, created 40d ago → recommend WARM."""
|
|
122
|
-
from lifecycle.lifecycle_evaluator import LifecycleEvaluator
|
|
123
|
-
evaluator = LifecycleEvaluator(self.db_path)
|
|
124
|
-
rec = evaluator.evaluate_single(6)
|
|
125
|
-
assert rec is not None
|
|
126
|
-
assert rec["to_state"] == "warm"
|
|
127
|
-
|
|
128
|
-
def test_retention_override_skips_memory(self):
|
|
129
|
-
"""Memory 1 should be skipped when in retention_overrides set."""
|
|
130
|
-
from lifecycle.lifecycle_evaluator import LifecycleEvaluator
|
|
131
|
-
evaluator = LifecycleEvaluator(self.db_path)
|
|
132
|
-
rec = evaluator.evaluate_single(1, retention_overrides={1})
|
|
133
|
-
assert rec is None
|
|
134
|
-
|
|
135
|
-
def test_evaluate_memories_returns_recommendations(self):
|
|
136
|
-
"""Full scan should return list with recommendations for eligible memories."""
|
|
137
|
-
from lifecycle.lifecycle_evaluator import LifecycleEvaluator
|
|
138
|
-
evaluator = LifecycleEvaluator(self.db_path)
|
|
139
|
-
recs = evaluator.evaluate_memories()
|
|
140
|
-
# Should recommend: Memory 1 (active→warm), 4 (warm→cold), 5 (cold→archived), 6 (active→warm)
|
|
141
|
-
assert isinstance(recs, list)
|
|
142
|
-
assert len(recs) >= 3 # At least memories 1, 4, 5
|
|
143
|
-
rec_ids = {r["memory_id"] for r in recs}
|
|
144
|
-
assert 1 in rec_ids # stale active
|
|
145
|
-
assert 4 in rec_ids # stale warm
|
|
146
|
-
assert 5 in rec_ids # stale cold
|
|
147
|
-
|
|
148
|
-
def test_evaluate_memories_excludes_retained(self):
|
|
149
|
-
"""evaluate_memories with retention_overrides skips those memory IDs."""
|
|
150
|
-
from lifecycle.lifecycle_evaluator import LifecycleEvaluator
|
|
151
|
-
evaluator = LifecycleEvaluator(self.db_path)
|
|
152
|
-
recs = evaluator.evaluate_memories(retention_overrides={1, 4})
|
|
153
|
-
rec_ids = {r["memory_id"] for r in recs}
|
|
154
|
-
assert 1 not in rec_ids
|
|
155
|
-
assert 4 not in rec_ids
|
|
156
|
-
assert 5 in rec_ids # cold→archived not overridden
|
|
157
|
-
|
|
158
|
-
def test_custom_config_thresholds(self):
|
|
159
|
-
"""Custom config should override default thresholds."""
|
|
160
|
-
from lifecycle.lifecycle_evaluator import LifecycleEvaluator
|
|
161
|
-
# Write custom config: raise active_to_warm threshold to 50 days
|
|
162
|
-
config_path = os.path.join(self.tmp_dir, "lifecycle_config.json")
|
|
163
|
-
with open(config_path, "w") as f:
|
|
164
|
-
json.dump({
|
|
165
|
-
"active_to_warm": {"no_access_days": 50, "max_importance": 6}
|
|
166
|
-
}, f)
|
|
167
|
-
evaluator = LifecycleEvaluator(self.db_path, config_path=config_path)
|
|
168
|
-
# Memory 1 is stale 35d — below new 50d threshold → no recommendation
|
|
169
|
-
rec = evaluator.evaluate_single(1)
|
|
170
|
-
assert rec is None
|
|
171
|
-
|
|
172
|
-
def test_evaluate_single_nonexistent_memory(self):
|
|
173
|
-
"""Evaluating a nonexistent memory returns None."""
|
|
174
|
-
from lifecycle.lifecycle_evaluator import LifecycleEvaluator
|
|
175
|
-
evaluator = LifecycleEvaluator(self.db_path)
|
|
176
|
-
rec = evaluator.evaluate_single(999)
|
|
177
|
-
assert rec is None
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Tests for lifecycle background scheduler.
|
|
4
|
-
"""
|
|
5
|
-
import sqlite3
|
|
6
|
-
import tempfile
|
|
7
|
-
import os
|
|
8
|
-
import sys
|
|
9
|
-
import time
|
|
10
|
-
import threading
|
|
11
|
-
from datetime import datetime, timedelta
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
|
|
14
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class TestLifecycleScheduler:
|
|
18
|
-
"""Test lifecycle scheduler background evaluation."""
|
|
19
|
-
|
|
20
|
-
def setup_method(self):
|
|
21
|
-
self.tmp_dir = tempfile.mkdtemp()
|
|
22
|
-
self.db_path = os.path.join(self.tmp_dir, "test.db")
|
|
23
|
-
conn = sqlite3.connect(self.db_path)
|
|
24
|
-
conn.execute("""
|
|
25
|
-
CREATE TABLE memories (
|
|
26
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
27
|
-
content TEXT NOT NULL,
|
|
28
|
-
importance INTEGER DEFAULT 5,
|
|
29
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
30
|
-
last_accessed TIMESTAMP,
|
|
31
|
-
access_count INTEGER DEFAULT 0,
|
|
32
|
-
lifecycle_state TEXT DEFAULT 'active',
|
|
33
|
-
lifecycle_updated_at TIMESTAMP,
|
|
34
|
-
lifecycle_history TEXT DEFAULT '[]',
|
|
35
|
-
access_level TEXT DEFAULT 'public',
|
|
36
|
-
profile TEXT DEFAULT 'default'
|
|
37
|
-
)
|
|
38
|
-
""")
|
|
39
|
-
now = datetime.now()
|
|
40
|
-
# Insert a stale memory that should be evaluated for transition
|
|
41
|
-
conn.execute(
|
|
42
|
-
"INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at) VALUES (?, ?, ?, ?, ?)",
|
|
43
|
-
("stale memory", 3, "active", (now - timedelta(days=45)).isoformat(), (now - timedelta(days=100)).isoformat()),
|
|
44
|
-
)
|
|
45
|
-
# Insert a fresh memory that should stay
|
|
46
|
-
conn.execute(
|
|
47
|
-
"INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at) VALUES (?, ?, ?, ?, ?)",
|
|
48
|
-
("fresh memory", 8, "active", now.isoformat(), (now - timedelta(days=10)).isoformat()),
|
|
49
|
-
)
|
|
50
|
-
conn.commit()
|
|
51
|
-
conn.close()
|
|
52
|
-
|
|
53
|
-
def teardown_method(self):
|
|
54
|
-
import shutil
|
|
55
|
-
shutil.rmtree(self.tmp_dir, ignore_errors=True)
|
|
56
|
-
|
|
57
|
-
def test_scheduler_creation(self):
|
|
58
|
-
"""Scheduler can be created with default settings."""
|
|
59
|
-
from lifecycle.lifecycle_scheduler import LifecycleScheduler
|
|
60
|
-
scheduler = LifecycleScheduler(self.db_path)
|
|
61
|
-
assert scheduler is not None
|
|
62
|
-
assert scheduler.interval_seconds == 21600 # 6 hours default
|
|
63
|
-
|
|
64
|
-
def test_run_now_executes_evaluation(self):
|
|
65
|
-
"""Manual trigger runs evaluation and transitions eligible memories."""
|
|
66
|
-
from lifecycle.lifecycle_scheduler import LifecycleScheduler
|
|
67
|
-
scheduler = LifecycleScheduler(self.db_path)
|
|
68
|
-
result = scheduler.run_now()
|
|
69
|
-
assert result is not None
|
|
70
|
-
assert "evaluation" in result
|
|
71
|
-
assert "enforcement" in result
|
|
72
|
-
|
|
73
|
-
def test_run_now_transitions_stale_memories(self):
|
|
74
|
-
"""run_now should transition stale memories."""
|
|
75
|
-
from lifecycle.lifecycle_scheduler import LifecycleScheduler
|
|
76
|
-
scheduler = LifecycleScheduler(self.db_path)
|
|
77
|
-
result = scheduler.run_now()
|
|
78
|
-
# Memory 1 (stale 45d, importance 3) should be recommended for transition
|
|
79
|
-
eval_recs = result["evaluation"]["recommendations"]
|
|
80
|
-
if eval_recs:
|
|
81
|
-
transitioned = result["evaluation"]["transitioned"]
|
|
82
|
-
assert transitioned >= 1
|
|
83
|
-
|
|
84
|
-
def test_fresh_memory_stays_active(self):
|
|
85
|
-
"""Fresh high-importance memory should NOT be transitioned."""
|
|
86
|
-
from lifecycle.lifecycle_scheduler import LifecycleScheduler
|
|
87
|
-
scheduler = LifecycleScheduler(self.db_path)
|
|
88
|
-
scheduler.run_now()
|
|
89
|
-
conn = sqlite3.connect(self.db_path)
|
|
90
|
-
row = conn.execute("SELECT lifecycle_state FROM memories WHERE id=2").fetchone()
|
|
91
|
-
conn.close()
|
|
92
|
-
assert row[0] == "active"
|
|
93
|
-
|
|
94
|
-
def test_scheduler_thread_is_daemon(self):
|
|
95
|
-
"""Scheduler thread should be daemonic (doesn't prevent exit)."""
|
|
96
|
-
from lifecycle.lifecycle_scheduler import LifecycleScheduler
|
|
97
|
-
scheduler = LifecycleScheduler(self.db_path, interval_seconds=3600)
|
|
98
|
-
scheduler.start()
|
|
99
|
-
assert scheduler._timer is not None
|
|
100
|
-
assert scheduler._timer.daemon is True
|
|
101
|
-
scheduler.stop()
|
|
102
|
-
|
|
103
|
-
def test_start_and_stop(self):
|
|
104
|
-
"""Scheduler can be started and stopped."""
|
|
105
|
-
from lifecycle.lifecycle_scheduler import LifecycleScheduler
|
|
106
|
-
scheduler = LifecycleScheduler(self.db_path, interval_seconds=3600)
|
|
107
|
-
scheduler.start()
|
|
108
|
-
assert scheduler.is_running is True
|
|
109
|
-
scheduler.stop()
|
|
110
|
-
assert scheduler.is_running is False
|
|
111
|
-
|
|
112
|
-
def test_configurable_interval(self):
|
|
113
|
-
"""Scheduler interval is configurable."""
|
|
114
|
-
from lifecycle.lifecycle_scheduler import LifecycleScheduler
|
|
115
|
-
scheduler = LifecycleScheduler(self.db_path, interval_seconds=7200)
|
|
116
|
-
assert scheduler.interval_seconds == 7200
|
|
117
|
-
|
|
118
|
-
def test_result_structure(self):
|
|
119
|
-
"""run_now returns properly structured result."""
|
|
120
|
-
from lifecycle.lifecycle_scheduler import LifecycleScheduler
|
|
121
|
-
scheduler = LifecycleScheduler(self.db_path)
|
|
122
|
-
result = scheduler.run_now()
|
|
123
|
-
assert "evaluation" in result
|
|
124
|
-
assert "enforcement" in result
|
|
125
|
-
assert "timestamp" in result
|
|
126
|
-
assert "recommendations" in result["evaluation"]
|
|
127
|
-
assert "transitioned" in result["evaluation"]
|