superlocalmemory 2.8.6 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -1
- package/NOTICE +63 -0
- package/README.md +165 -480
- package/bin/slm +17 -449
- package/bin/slm-npm +1 -1
- 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
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""SuperLocalMemory V3 — Behavioral Pattern Store.
|
|
6
|
+
|
|
7
|
+
Stores, retrieves, and transfers behavioral patterns per profile.
|
|
8
|
+
Ported from V2's _store_patterns.py + cross_project_transfer.py
|
|
9
|
+
into a single unified module with direct sqlite3 access.
|
|
10
|
+
|
|
11
|
+
Key features:
|
|
12
|
+
- Record detected patterns (refinement, interest, archival, etc.)
|
|
13
|
+
- Query patterns by profile and type
|
|
14
|
+
- Summarize pattern counts by type
|
|
15
|
+
- Transfer patterns across profiles (cross-project learning)
|
|
16
|
+
- Confidence scoring: min(evidence/10, 1.0) * abs(rate - 0.5) * 2
|
|
17
|
+
|
|
18
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import logging
|
|
25
|
+
import sqlite3
|
|
26
|
+
import threading
|
|
27
|
+
from datetime import datetime, timezone
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Any, Dict, List, Optional
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
# Minimum observations before emitting a pattern
|
|
34
|
+
MIN_EVIDENCE = 3
|
|
35
|
+
|
|
36
|
+
# Transfer eligibility thresholds
|
|
37
|
+
TRANSFER_MIN_CONFIDENCE = 0.3
|
|
38
|
+
TRANSFER_MIN_EVIDENCE = 2
|
|
39
|
+
|
|
40
|
+
_CREATE_TABLE = """
|
|
41
|
+
CREATE TABLE IF NOT EXISTS _store_patterns (
|
|
42
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
43
|
+
profile_id TEXT NOT NULL,
|
|
44
|
+
pattern_type TEXT NOT NULL,
|
|
45
|
+
pattern_key TEXT DEFAULT '',
|
|
46
|
+
success_rate REAL DEFAULT 0.0,
|
|
47
|
+
evidence_count INTEGER DEFAULT 1,
|
|
48
|
+
confidence REAL DEFAULT 0.0,
|
|
49
|
+
metadata TEXT DEFAULT '{}',
|
|
50
|
+
created_at TEXT NOT NULL,
|
|
51
|
+
updated_at TEXT NOT NULL
|
|
52
|
+
)
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
_CREATE_INDEX = """
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_sp_profile_type
|
|
57
|
+
ON _store_patterns(profile_id, pattern_type)
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class BehavioralPatternStore:
|
|
62
|
+
"""Store and query behavioral patterns per profile.
|
|
63
|
+
|
|
64
|
+
Uses direct sqlite3 for storage. Thread-safe via a lock.
|
|
65
|
+
Creates the _store_patterns table on first use.
|
|
66
|
+
|
|
67
|
+
Ported from V2's BehavioralPatternExtractor + CrossProjectTransfer,
|
|
68
|
+
unified into a single class with profile-scoped operations.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, db_path: str | Path) -> None:
|
|
72
|
+
self._db_path = str(db_path)
|
|
73
|
+
self._lock = threading.Lock()
|
|
74
|
+
self._ensure_table()
|
|
75
|
+
|
|
76
|
+
# ------------------------------------------------------------------
|
|
77
|
+
# Public API
|
|
78
|
+
# ------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
def record_pattern(
|
|
81
|
+
self,
|
|
82
|
+
profile_id: str,
|
|
83
|
+
pattern_type: str,
|
|
84
|
+
data: Optional[Dict[str, Any]] = None,
|
|
85
|
+
success_rate: float = 0.0,
|
|
86
|
+
confidence: float = 0.0,
|
|
87
|
+
) -> int:
|
|
88
|
+
"""Store a detected behavioral pattern.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
profile_id: Profile scope for the pattern.
|
|
92
|
+
pattern_type: Category (e.g. "refinement", "interest", "archival").
|
|
93
|
+
data: Arbitrary metadata dict (stored as JSON).
|
|
94
|
+
success_rate: Success rate if applicable (0.0-1.0).
|
|
95
|
+
confidence: Confidence score (0.0-1.0).
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
The row ID of the inserted pattern.
|
|
99
|
+
"""
|
|
100
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
101
|
+
metadata_json = json.dumps(data or {})
|
|
102
|
+
pattern_key = (data or {}).get("topic", (data or {}).get("pattern_key", ""))
|
|
103
|
+
|
|
104
|
+
with self._lock:
|
|
105
|
+
conn = self._connect()
|
|
106
|
+
try:
|
|
107
|
+
# Check for existing pattern of same type+key for this profile
|
|
108
|
+
existing = conn.execute(
|
|
109
|
+
"SELECT id, evidence_count FROM _store_patterns "
|
|
110
|
+
"WHERE profile_id = ? AND pattern_type = ? AND pattern_key = ?",
|
|
111
|
+
(profile_id, pattern_type, pattern_key),
|
|
112
|
+
).fetchone()
|
|
113
|
+
|
|
114
|
+
if existing:
|
|
115
|
+
new_count = existing[1] + 1
|
|
116
|
+
new_confidence = self._compute_confidence(
|
|
117
|
+
new_count, success_rate
|
|
118
|
+
) if success_rate > 0 else min(1.0, new_count / 100.0)
|
|
119
|
+
conn.execute(
|
|
120
|
+
"UPDATE _store_patterns "
|
|
121
|
+
"SET evidence_count = ?, confidence = ?, "
|
|
122
|
+
" success_rate = ?, metadata = ?, updated_at = ? "
|
|
123
|
+
"WHERE id = ?",
|
|
124
|
+
(new_count, new_confidence, success_rate,
|
|
125
|
+
metadata_json, now, existing[0]),
|
|
126
|
+
)
|
|
127
|
+
conn.commit()
|
|
128
|
+
return existing[0]
|
|
129
|
+
|
|
130
|
+
initial_confidence = confidence or 0.01
|
|
131
|
+
cur = conn.execute(
|
|
132
|
+
"INSERT INTO _store_patterns "
|
|
133
|
+
"(profile_id, pattern_type, pattern_key, success_rate, "
|
|
134
|
+
" evidence_count, confidence, metadata, created_at, updated_at) "
|
|
135
|
+
"VALUES (?, ?, ?, ?, 1, ?, ?, ?, ?)",
|
|
136
|
+
(profile_id, pattern_type, pattern_key, success_rate,
|
|
137
|
+
initial_confidence, metadata_json, now, now),
|
|
138
|
+
)
|
|
139
|
+
conn.commit()
|
|
140
|
+
return cur.lastrowid
|
|
141
|
+
finally:
|
|
142
|
+
conn.close()
|
|
143
|
+
|
|
144
|
+
def get_patterns(
|
|
145
|
+
self,
|
|
146
|
+
profile_id: str,
|
|
147
|
+
pattern_type: Optional[str] = None,
|
|
148
|
+
limit: int = 50,
|
|
149
|
+
min_confidence: float = 0.0,
|
|
150
|
+
) -> List[Dict[str, Any]]:
|
|
151
|
+
"""Get stored patterns for a profile.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
profile_id: Profile to query.
|
|
155
|
+
pattern_type: If given, filter by type.
|
|
156
|
+
limit: Max rows to return.
|
|
157
|
+
min_confidence: Minimum confidence threshold.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
List of pattern dicts with deserialized metadata.
|
|
161
|
+
"""
|
|
162
|
+
with self._lock:
|
|
163
|
+
conn = self._connect()
|
|
164
|
+
try:
|
|
165
|
+
query = (
|
|
166
|
+
"SELECT * FROM _store_patterns "
|
|
167
|
+
"WHERE profile_id = ? AND confidence >= ?"
|
|
168
|
+
)
|
|
169
|
+
params: List[Any] = [profile_id, min_confidence]
|
|
170
|
+
|
|
171
|
+
if pattern_type is not None:
|
|
172
|
+
query += " AND pattern_type = ?"
|
|
173
|
+
params.append(pattern_type)
|
|
174
|
+
|
|
175
|
+
query += " ORDER BY confidence DESC, updated_at DESC LIMIT ?"
|
|
176
|
+
params.append(limit)
|
|
177
|
+
|
|
178
|
+
rows = conn.execute(query, params).fetchall()
|
|
179
|
+
return [self._row_to_dict(r) for r in rows]
|
|
180
|
+
finally:
|
|
181
|
+
conn.close()
|
|
182
|
+
|
|
183
|
+
def get_summary(self, profile_id: str) -> Dict[str, int]:
|
|
184
|
+
"""Get pattern counts by type for a profile.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Dict mapping pattern_type -> count.
|
|
188
|
+
"""
|
|
189
|
+
with self._lock:
|
|
190
|
+
conn = self._connect()
|
|
191
|
+
try:
|
|
192
|
+
rows = conn.execute(
|
|
193
|
+
"SELECT pattern_type, COUNT(*) as cnt "
|
|
194
|
+
"FROM _store_patterns "
|
|
195
|
+
"WHERE profile_id = ? "
|
|
196
|
+
"GROUP BY pattern_type",
|
|
197
|
+
(profile_id,),
|
|
198
|
+
).fetchall()
|
|
199
|
+
return {row[0]: row[1] for row in rows}
|
|
200
|
+
finally:
|
|
201
|
+
conn.close()
|
|
202
|
+
|
|
203
|
+
def transfer_patterns(
|
|
204
|
+
self,
|
|
205
|
+
source_profile: str,
|
|
206
|
+
target_profile: str,
|
|
207
|
+
min_confidence: float = 0.0,
|
|
208
|
+
) -> int:
|
|
209
|
+
"""Copy eligible patterns from source to target profile.
|
|
210
|
+
|
|
211
|
+
Only metadata (type, key, success_rate, confidence) is transferred.
|
|
212
|
+
Memory content is never transferred. Creates new rows in the target
|
|
213
|
+
profile scope.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
source_profile: Profile to copy patterns from.
|
|
217
|
+
target_profile: Profile to copy patterns to.
|
|
218
|
+
min_confidence: Only transfer patterns above this threshold.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Number of patterns transferred.
|
|
222
|
+
"""
|
|
223
|
+
if source_profile == target_profile:
|
|
224
|
+
return 0
|
|
225
|
+
|
|
226
|
+
source_patterns = self.get_patterns(
|
|
227
|
+
source_profile, min_confidence=min_confidence
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
transferred = 0
|
|
231
|
+
for pattern in source_patterns:
|
|
232
|
+
# Skip if target already has this pattern
|
|
233
|
+
existing = self.get_patterns(
|
|
234
|
+
target_profile,
|
|
235
|
+
pattern_type=pattern["pattern_type"],
|
|
236
|
+
)
|
|
237
|
+
already_exists = any(
|
|
238
|
+
p["pattern_key"] == pattern.get("pattern_key", "")
|
|
239
|
+
for p in existing
|
|
240
|
+
)
|
|
241
|
+
if already_exists:
|
|
242
|
+
continue
|
|
243
|
+
|
|
244
|
+
self.record_pattern(
|
|
245
|
+
profile_id=target_profile,
|
|
246
|
+
pattern_type=pattern["pattern_type"],
|
|
247
|
+
data={
|
|
248
|
+
"topic": pattern.get("pattern_key", ""),
|
|
249
|
+
"transferred_from": source_profile,
|
|
250
|
+
"original_confidence": pattern.get("confidence", 0.0),
|
|
251
|
+
},
|
|
252
|
+
success_rate=pattern.get("success_rate", 0.0),
|
|
253
|
+
confidence=pattern.get("confidence", 0.0) * 0.8,
|
|
254
|
+
)
|
|
255
|
+
transferred += 1
|
|
256
|
+
|
|
257
|
+
return transferred
|
|
258
|
+
|
|
259
|
+
def delete_patterns(
|
|
260
|
+
self,
|
|
261
|
+
profile_id: str,
|
|
262
|
+
pattern_type: Optional[str] = None,
|
|
263
|
+
) -> int:
|
|
264
|
+
"""Delete patterns for a profile. Optionally filter by type.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
Number of patterns deleted.
|
|
268
|
+
"""
|
|
269
|
+
with self._lock:
|
|
270
|
+
conn = self._connect()
|
|
271
|
+
try:
|
|
272
|
+
if pattern_type:
|
|
273
|
+
cur = conn.execute(
|
|
274
|
+
"DELETE FROM _store_patterns "
|
|
275
|
+
"WHERE profile_id = ? AND pattern_type = ?",
|
|
276
|
+
(profile_id, pattern_type),
|
|
277
|
+
)
|
|
278
|
+
else:
|
|
279
|
+
cur = conn.execute(
|
|
280
|
+
"DELETE FROM _store_patterns WHERE profile_id = ?",
|
|
281
|
+
(profile_id,),
|
|
282
|
+
)
|
|
283
|
+
conn.commit()
|
|
284
|
+
return cur.rowcount
|
|
285
|
+
finally:
|
|
286
|
+
conn.close()
|
|
287
|
+
|
|
288
|
+
# ------------------------------------------------------------------
|
|
289
|
+
# Internal helpers
|
|
290
|
+
# ------------------------------------------------------------------
|
|
291
|
+
|
|
292
|
+
@staticmethod
|
|
293
|
+
def _compute_confidence(evidence_count: int, success_rate: float) -> float:
|
|
294
|
+
"""Confidence = min(evidence/10, 1.0) * abs(rate - 0.5) * 2.
|
|
295
|
+
|
|
296
|
+
High confidence requires both sufficient evidence AND a success
|
|
297
|
+
rate that deviates significantly from the 50% baseline.
|
|
298
|
+
"""
|
|
299
|
+
evidence_factor = min(evidence_count / 10.0, 1.0)
|
|
300
|
+
deviation_factor = abs(success_rate - 0.5) * 2.0
|
|
301
|
+
return round(evidence_factor * deviation_factor, 4)
|
|
302
|
+
|
|
303
|
+
def _connect(self) -> sqlite3.Connection:
|
|
304
|
+
"""Open a connection with row factory enabled."""
|
|
305
|
+
conn = sqlite3.connect(self._db_path)
|
|
306
|
+
conn.row_factory = sqlite3.Row
|
|
307
|
+
return conn
|
|
308
|
+
|
|
309
|
+
def _ensure_table(self) -> None:
|
|
310
|
+
"""Create the _store_patterns table if it does not exist."""
|
|
311
|
+
conn = self._connect()
|
|
312
|
+
try:
|
|
313
|
+
conn.execute(_CREATE_TABLE)
|
|
314
|
+
conn.execute(_CREATE_INDEX)
|
|
315
|
+
conn.commit()
|
|
316
|
+
finally:
|
|
317
|
+
conn.close()
|
|
318
|
+
|
|
319
|
+
@staticmethod
|
|
320
|
+
def _row_to_dict(row: sqlite3.Row) -> Dict[str, Any]:
|
|
321
|
+
"""Convert a sqlite3.Row into a plain dict with parsed JSON."""
|
|
322
|
+
d = dict(row)
|
|
323
|
+
meta = d.get("metadata", "{}")
|
|
324
|
+
d["metadata"] = json.loads(meta) if isinstance(meta, str) else meta
|
|
325
|
+
return d
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
# ---------------------------------------------------------------------------
|
|
329
|
+
# V3 API — BehavioralTracker (uses DatabaseManager + V3 schema)
|
|
330
|
+
# ---------------------------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
from superlocalmemory.storage.models import BehavioralPattern # noqa: E402
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class BehavioralTracker:
|
|
336
|
+
"""V3 behavioral pattern tracker using DatabaseManager.
|
|
337
|
+
|
|
338
|
+
Records query patterns (time of day, query type, entity preferences)
|
|
339
|
+
and provides analytics (active hours, type distribution, preferences).
|
|
340
|
+
|
|
341
|
+
Uses the ``behavioral_patterns`` table from the V3 schema.
|
|
342
|
+
"""
|
|
343
|
+
|
|
344
|
+
def __init__(self, db) -> None:
|
|
345
|
+
self._db = db
|
|
346
|
+
|
|
347
|
+
# ------------------------------------------------------------------
|
|
348
|
+
# Recording
|
|
349
|
+
# ------------------------------------------------------------------
|
|
350
|
+
|
|
351
|
+
def record_query(
|
|
352
|
+
self,
|
|
353
|
+
query: str,
|
|
354
|
+
query_type: str,
|
|
355
|
+
entities: list[str],
|
|
356
|
+
profile_id: str,
|
|
357
|
+
) -> None:
|
|
358
|
+
"""Record a query and extract behavioral patterns.
|
|
359
|
+
|
|
360
|
+
Creates up to 3 pattern types per call:
|
|
361
|
+
- ``time_of_day``: hour_N for current hour
|
|
362
|
+
- ``query_type``: keyed by the query_type string
|
|
363
|
+
- ``entity_pref``: one per entity (max 5, lowercased)
|
|
364
|
+
"""
|
|
365
|
+
# 1. Time of day
|
|
366
|
+
hour = datetime.now(timezone.utc).hour
|
|
367
|
+
self._upsert_pattern(profile_id, "time_of_day", f"hour_{hour}")
|
|
368
|
+
|
|
369
|
+
# 2. Query type
|
|
370
|
+
if query_type:
|
|
371
|
+
self._upsert_pattern(profile_id, "query_type", query_type)
|
|
372
|
+
|
|
373
|
+
# 3. Entity preferences (max 5, lowercased)
|
|
374
|
+
for entity in entities[:5]:
|
|
375
|
+
self._upsert_pattern(profile_id, "entity_pref", entity.lower())
|
|
376
|
+
|
|
377
|
+
# ------------------------------------------------------------------
|
|
378
|
+
# Querying
|
|
379
|
+
# ------------------------------------------------------------------
|
|
380
|
+
|
|
381
|
+
def get_patterns(
|
|
382
|
+
self,
|
|
383
|
+
pattern_type: str,
|
|
384
|
+
profile_id: str,
|
|
385
|
+
min_confidence: float = 0.0,
|
|
386
|
+
) -> list[BehavioralPattern]:
|
|
387
|
+
"""Get patterns filtered by type, profile, and min confidence."""
|
|
388
|
+
rows = self._db.execute(
|
|
389
|
+
"SELECT * FROM behavioral_patterns "
|
|
390
|
+
"WHERE profile_id = ? AND pattern_type = ? AND confidence >= ? "
|
|
391
|
+
"ORDER BY confidence DESC",
|
|
392
|
+
(profile_id, pattern_type, min_confidence),
|
|
393
|
+
)
|
|
394
|
+
return [self._row_to_pattern(r) for r in rows]
|
|
395
|
+
|
|
396
|
+
def get_entity_preferences(
|
|
397
|
+
self, profile_id: str, top_k: int = 10
|
|
398
|
+
) -> list[str]:
|
|
399
|
+
"""Top-K preferred entities by confidence, highest first."""
|
|
400
|
+
rows = self._db.execute(
|
|
401
|
+
"SELECT pattern_key FROM behavioral_patterns "
|
|
402
|
+
"WHERE profile_id = ? AND pattern_type = 'entity_pref' "
|
|
403
|
+
"ORDER BY confidence DESC, observation_count DESC LIMIT ?",
|
|
404
|
+
(profile_id, top_k),
|
|
405
|
+
)
|
|
406
|
+
return [dict(r)["pattern_key"] for r in rows]
|
|
407
|
+
|
|
408
|
+
def get_active_hours(self, profile_id: str) -> list[int]:
|
|
409
|
+
"""Top 5 active hours by observation count."""
|
|
410
|
+
rows = self._db.execute(
|
|
411
|
+
"SELECT pattern_key FROM behavioral_patterns "
|
|
412
|
+
"WHERE profile_id = ? AND pattern_type = 'time_of_day' "
|
|
413
|
+
"ORDER BY observation_count DESC LIMIT 5",
|
|
414
|
+
(profile_id,),
|
|
415
|
+
)
|
|
416
|
+
result: list[int] = []
|
|
417
|
+
for r in rows:
|
|
418
|
+
key = dict(r)["pattern_key"]
|
|
419
|
+
if key.startswith("hour_"):
|
|
420
|
+
try:
|
|
421
|
+
result.append(int(key[5:]))
|
|
422
|
+
except ValueError:
|
|
423
|
+
pass
|
|
424
|
+
return result
|
|
425
|
+
|
|
426
|
+
def get_query_type_distribution(self, profile_id: str) -> dict[str, float]:
|
|
427
|
+
"""Proportional distribution of query types."""
|
|
428
|
+
rows = self._db.execute(
|
|
429
|
+
"SELECT pattern_key, observation_count FROM behavioral_patterns "
|
|
430
|
+
"WHERE profile_id = ? AND pattern_type = 'query_type'",
|
|
431
|
+
(profile_id,),
|
|
432
|
+
)
|
|
433
|
+
counts: dict[str, int] = {}
|
|
434
|
+
for r in rows:
|
|
435
|
+
d = dict(r)
|
|
436
|
+
counts[d["pattern_key"]] = d["observation_count"]
|
|
437
|
+
|
|
438
|
+
total = sum(counts.values())
|
|
439
|
+
if total == 0:
|
|
440
|
+
return {}
|
|
441
|
+
return {k: round(v / total, 4) for k, v in counts.items()}
|
|
442
|
+
|
|
443
|
+
# ------------------------------------------------------------------
|
|
444
|
+
# Internal
|
|
445
|
+
# ------------------------------------------------------------------
|
|
446
|
+
|
|
447
|
+
def _upsert_pattern(
|
|
448
|
+
self, profile_id: str, pattern_type: str, pattern_key: str
|
|
449
|
+
) -> None:
|
|
450
|
+
"""Insert or increment a pattern. Confidence = min(count/100, 1.0)."""
|
|
451
|
+
from superlocalmemory.storage.models import _new_id, _now
|
|
452
|
+
|
|
453
|
+
rows = self._db.execute(
|
|
454
|
+
"SELECT pattern_id, observation_count FROM behavioral_patterns "
|
|
455
|
+
"WHERE profile_id = ? AND pattern_type = ? AND pattern_key = ?",
|
|
456
|
+
(profile_id, pattern_type, pattern_key),
|
|
457
|
+
)
|
|
458
|
+
if rows:
|
|
459
|
+
d = dict(rows[0])
|
|
460
|
+
new_count = d["observation_count"] + 1
|
|
461
|
+
new_conf = min(new_count / 100.0, 1.0)
|
|
462
|
+
self._db.execute(
|
|
463
|
+
"UPDATE behavioral_patterns "
|
|
464
|
+
"SET observation_count = ?, confidence = ?, last_updated = ? "
|
|
465
|
+
"WHERE pattern_id = ?",
|
|
466
|
+
(new_count, new_conf, _now(), d["pattern_id"]),
|
|
467
|
+
)
|
|
468
|
+
else:
|
|
469
|
+
self._db.execute(
|
|
470
|
+
"INSERT INTO behavioral_patterns "
|
|
471
|
+
"(pattern_id, profile_id, pattern_type, pattern_key, "
|
|
472
|
+
" pattern_value, confidence, observation_count, last_updated) "
|
|
473
|
+
"VALUES (?, ?, ?, ?, '', ?, 1, ?)",
|
|
474
|
+
(_new_id(), profile_id, pattern_type, pattern_key, 0.01, _now()),
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
@staticmethod
|
|
478
|
+
def _row_to_pattern(row) -> BehavioralPattern:
|
|
479
|
+
"""Convert a DB row to BehavioralPattern."""
|
|
480
|
+
d = dict(row)
|
|
481
|
+
return BehavioralPattern(
|
|
482
|
+
pattern_id=d["pattern_id"],
|
|
483
|
+
profile_id=d["profile_id"],
|
|
484
|
+
pattern_type=d.get("pattern_type", ""),
|
|
485
|
+
pattern_key=d.get("pattern_key", ""),
|
|
486
|
+
pattern_value=d.get("pattern_value", ""),
|
|
487
|
+
confidence=d.get("confidence", 0.0),
|
|
488
|
+
observation_count=d.get("observation_count", 0),
|
|
489
|
+
last_updated=d.get("last_updated", ""),
|
|
490
|
+
)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""Behavioral listener — subscribes to event bus, captures patterns.
|
|
6
|
+
|
|
7
|
+
Connects to the V3 event bus and records all memory operations
|
|
8
|
+
for behavioral pattern mining.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import time
|
|
15
|
+
from collections import deque
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
# Max events kept in memory for pattern mining
|
|
21
|
+
MAX_EVENT_BUFFER = 500
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BehavioralListener:
|
|
25
|
+
"""Subscribes to event bus and records behavioral data."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, event_bus=None, db_path=None):
|
|
28
|
+
self._events = deque(maxlen=MAX_EVENT_BUFFER)
|
|
29
|
+
self._event_count = 0
|
|
30
|
+
self._db_path = db_path
|
|
31
|
+
|
|
32
|
+
if event_bus:
|
|
33
|
+
event_bus.subscribe(self._on_event)
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def event_count(self) -> int:
|
|
37
|
+
return self._event_count
|
|
38
|
+
|
|
39
|
+
def _on_event(self, event: dict) -> None:
|
|
40
|
+
"""Handle incoming event from bus."""
|
|
41
|
+
self._events.append({
|
|
42
|
+
"event_type": event.get("event_type", "unknown"),
|
|
43
|
+
"data": event.get("data", {}),
|
|
44
|
+
"timestamp": time.time(),
|
|
45
|
+
})
|
|
46
|
+
self._event_count += 1
|
|
47
|
+
|
|
48
|
+
def get_recent_events(self, limit: int = 50) -> list[dict]:
|
|
49
|
+
"""Get most recent behavioral events."""
|
|
50
|
+
return list(self._events)[-limit:]
|
|
51
|
+
|
|
52
|
+
def mine_patterns(self) -> list[dict]:
|
|
53
|
+
"""Mine behavioral patterns from recent events.
|
|
54
|
+
|
|
55
|
+
Detects:
|
|
56
|
+
- store->recall->store = refinement pattern
|
|
57
|
+
- repeated recall of same topic = interest pattern
|
|
58
|
+
- store without recall = archival pattern
|
|
59
|
+
"""
|
|
60
|
+
patterns = []
|
|
61
|
+
events = list(self._events)
|
|
62
|
+
|
|
63
|
+
# Detect store->recall->store (refinement)
|
|
64
|
+
for i in range(len(events) - 2):
|
|
65
|
+
if (events[i]["event_type"] == "memory.stored" and
|
|
66
|
+
events[i+1]["event_type"] == "memory.recalled" and
|
|
67
|
+
events[i+2]["event_type"] == "memory.stored"):
|
|
68
|
+
patterns.append({
|
|
69
|
+
"pattern_type": "refinement",
|
|
70
|
+
"timestamp": events[i+2]["timestamp"],
|
|
71
|
+
"events": [events[i], events[i+1], events[i+2]],
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
# Detect repeated recall (interest)
|
|
75
|
+
recall_topics = {}
|
|
76
|
+
for e in events:
|
|
77
|
+
if e["event_type"] == "memory.recalled":
|
|
78
|
+
topic = e["data"].get("query_preview", "")[:50]
|
|
79
|
+
recall_topics[topic] = recall_topics.get(topic, 0) + 1
|
|
80
|
+
|
|
81
|
+
for topic, count in recall_topics.items():
|
|
82
|
+
if count >= 3:
|
|
83
|
+
patterns.append({
|
|
84
|
+
"pattern_type": "interest",
|
|
85
|
+
"topic": topic,
|
|
86
|
+
"count": count,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
return patterns
|
|
90
|
+
|
|
91
|
+
def clear(self) -> None:
|
|
92
|
+
"""Clear event buffer."""
|
|
93
|
+
self._events.clear()
|
|
94
|
+
self._event_count = 0
|