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,232 @@
|
|
|
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 — Semantic Retrieval Channel.
|
|
6
|
+
|
|
7
|
+
Fisher-aware semantic search: uses Fisher-Rao geodesic distance when
|
|
8
|
+
variance data is available, falls back to cosine similarity otherwise.
|
|
9
|
+
|
|
10
|
+
The Fisher distance is meaningful when memories accumulate evidence
|
|
11
|
+
(repeated confirmation narrows variance). For fresh benchmark data
|
|
12
|
+
where all variances are identical, Fisher distance degenerates to a
|
|
13
|
+
monotonic transform of Euclidean distance — same ranking as cosine.
|
|
14
|
+
|
|
15
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
16
|
+
License: MIT
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
import math
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
24
|
+
|
|
25
|
+
import numpy as np
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from superlocalmemory.storage.database import DatabaseManager
|
|
29
|
+
from superlocalmemory.storage.models import AtomicFact
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
# Minimum variance floor to prevent division-by-zero in Fisher distance
|
|
34
|
+
_VARIANCE_FLOOR: float = 1e-6
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
|
|
38
|
+
"""Cosine similarity in [-1, 1]. Returns 0.0 on zero vectors."""
|
|
39
|
+
norm_a = np.linalg.norm(a)
|
|
40
|
+
norm_b = np.linalg.norm(b)
|
|
41
|
+
if norm_a < _VARIANCE_FLOOR or norm_b < _VARIANCE_FLOOR:
|
|
42
|
+
return 0.0
|
|
43
|
+
return float(np.dot(a, b) / (norm_a * norm_b))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _fisher_rao_similarity(
|
|
47
|
+
mu_q: np.ndarray,
|
|
48
|
+
mu_f: np.ndarray,
|
|
49
|
+
var_f: np.ndarray,
|
|
50
|
+
temperature: float = 15.0,
|
|
51
|
+
) -> float:
|
|
52
|
+
"""Fisher-Rao geodesic similarity on diagonal Gaussian manifold.
|
|
53
|
+
|
|
54
|
+
d_FR^2 = sum_i [ (mu_q_i - mu_f_i)^2 / max(var_f_i, eps) ]
|
|
55
|
+
similarity = exp(-d_FR^2 / temperature)
|
|
56
|
+
|
|
57
|
+
When all variances are equal, this is equivalent to a scaled
|
|
58
|
+
Euclidean distance — same ranking as cosine on normalized vectors.
|
|
59
|
+
The advantage appears when variances differ across memories.
|
|
60
|
+
"""
|
|
61
|
+
diff = mu_q - mu_f
|
|
62
|
+
var_safe = np.maximum(var_f, _VARIANCE_FLOOR)
|
|
63
|
+
d_sq = float(np.sum(diff * diff / var_safe))
|
|
64
|
+
return math.exp(-d_sq / temperature)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class SemanticChannel:
|
|
68
|
+
"""Dense semantic retrieval via embedding similarity.
|
|
69
|
+
|
|
70
|
+
Scans all facts for a profile. Uses a GRADUATED Fisher-Rao ramp:
|
|
71
|
+
fresh facts (low access_count) use cosine, frequently-accessed facts
|
|
72
|
+
transition to Fisher-Rao distance for uncertainty-aware similarity.
|
|
73
|
+
|
|
74
|
+
Graduated ramp: weight = min(1.2, access_count / 10 * 1.2)
|
|
75
|
+
Final sim = fisher_weight * fisher_sim + (1 - fisher_weight) * cosine_sim
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
db: DatabaseManager,
|
|
81
|
+
fisher_temperature: float = 15.0,
|
|
82
|
+
embedder: object | None = None,
|
|
83
|
+
fisher_mode: str = "simplified",
|
|
84
|
+
) -> None:
|
|
85
|
+
self._db = db
|
|
86
|
+
self._temperature = fisher_temperature
|
|
87
|
+
self._embedder = embedder
|
|
88
|
+
self._fisher_mode = fisher_mode if fisher_mode in ("simplified", "full") else "simplified"
|
|
89
|
+
# Lazily instantiated full metric (avoids import cost when not needed)
|
|
90
|
+
self._full_metric: object | None = None
|
|
91
|
+
|
|
92
|
+
def search(
|
|
93
|
+
self,
|
|
94
|
+
query_embedding: list[float],
|
|
95
|
+
profile_id: str,
|
|
96
|
+
top_k: int = 50,
|
|
97
|
+
) -> list[tuple[str, float]]:
|
|
98
|
+
"""Search for semantically similar facts.
|
|
99
|
+
|
|
100
|
+
Uses graduated Fisher-Rao ramp: access_count < 1 = pure cosine,
|
|
101
|
+
access_count >= 10 = full Fisher-Rao (1.2x weight).
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
query_embedding: Dense vector for the query.
|
|
105
|
+
profile_id: Scope to this profile.
|
|
106
|
+
top_k: Maximum results to return.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
List of (fact_id, score) sorted by score descending.
|
|
110
|
+
Score is in [0, 1] range.
|
|
111
|
+
"""
|
|
112
|
+
if not query_embedding:
|
|
113
|
+
return []
|
|
114
|
+
|
|
115
|
+
q_vec = np.array(query_embedding, dtype=np.float32)
|
|
116
|
+
|
|
117
|
+
# Compute query Fisher params for Bayesian comparison (F45 fix)
|
|
118
|
+
q_mean: np.ndarray | None = None
|
|
119
|
+
q_var: np.ndarray | None = None
|
|
120
|
+
if self._embedder and hasattr(self._embedder, 'compute_fisher_params'):
|
|
121
|
+
qm, qv = self._embedder.compute_fisher_params(query_embedding)
|
|
122
|
+
q_mean = np.array(qm, dtype=np.float32)
|
|
123
|
+
q_var = np.array(qv, dtype=np.float32)
|
|
124
|
+
|
|
125
|
+
facts = self._db.get_all_facts(profile_id)
|
|
126
|
+
|
|
127
|
+
scored: list[tuple[str, float]] = []
|
|
128
|
+
for fact in facts:
|
|
129
|
+
if fact.embedding is None:
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
f_vec = np.array(fact.embedding, dtype=np.float32)
|
|
133
|
+
if f_vec.shape != q_vec.shape:
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
# Cosine baseline (always computed)
|
|
137
|
+
cos_sim = (_cosine_similarity(q_vec, f_vec) + 1.0) / 2.0
|
|
138
|
+
|
|
139
|
+
# Graduated Fisher-Rao ramp (F37, F108)
|
|
140
|
+
fisher_weight = min(1.2, (fact.access_count or 0) / 10.0 * 1.2)
|
|
141
|
+
|
|
142
|
+
if (fisher_weight > 0.01
|
|
143
|
+
and fact.fisher_variance is not None
|
|
144
|
+
and len(fact.fisher_variance) == len(q_vec)):
|
|
145
|
+
var_vec = np.array(fact.fisher_variance, dtype=np.float32)
|
|
146
|
+
f_sim = self._compute_fisher_sim(
|
|
147
|
+
q_vec, f_vec, var_vec, fact, q_mean, q_var,
|
|
148
|
+
)
|
|
149
|
+
capped_w = min(1.0, fisher_weight)
|
|
150
|
+
sim = capped_w * f_sim + (1.0 - capped_w) * cos_sim
|
|
151
|
+
else:
|
|
152
|
+
sim = cos_sim
|
|
153
|
+
|
|
154
|
+
if sim > 0.3:
|
|
155
|
+
scored.append((fact.fact_id, sim))
|
|
156
|
+
|
|
157
|
+
scored.sort(key=lambda x: x[1], reverse=True)
|
|
158
|
+
return scored[:top_k]
|
|
159
|
+
|
|
160
|
+
# ------------------------------------------------------------------
|
|
161
|
+
# Fisher similarity dispatch
|
|
162
|
+
# ------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
def _compute_fisher_sim(
|
|
165
|
+
self,
|
|
166
|
+
q_vec: np.ndarray,
|
|
167
|
+
f_vec: np.ndarray,
|
|
168
|
+
var_vec: np.ndarray,
|
|
169
|
+
fact: AtomicFact,
|
|
170
|
+
q_mean: np.ndarray | None,
|
|
171
|
+
q_var: np.ndarray | None,
|
|
172
|
+
) -> float:
|
|
173
|
+
"""Compute Fisher-Rao similarity using simplified or full metric.
|
|
174
|
+
|
|
175
|
+
Simplified (default): Mahalanobis-like distance using only fact variance.
|
|
176
|
+
Full: Atkinson-Mitchell geodesic via FisherRaoMetric.similarity(),
|
|
177
|
+
requires both query and fact (mean, variance) pairs.
|
|
178
|
+
|
|
179
|
+
Falls back to simplified if full metric cannot be applied (e.g.
|
|
180
|
+
missing fisher_mean on the fact, or missing query variance).
|
|
181
|
+
"""
|
|
182
|
+
if self._fisher_mode == "full":
|
|
183
|
+
return self._compute_full_fisher_sim(
|
|
184
|
+
q_vec, f_vec, var_vec, fact, q_mean, q_var,
|
|
185
|
+
)
|
|
186
|
+
return _fisher_rao_similarity(q_vec, f_vec, var_vec, self._temperature)
|
|
187
|
+
|
|
188
|
+
def _compute_full_fisher_sim(
|
|
189
|
+
self,
|
|
190
|
+
q_vec: np.ndarray,
|
|
191
|
+
f_vec: np.ndarray,
|
|
192
|
+
var_vec: np.ndarray,
|
|
193
|
+
fact: AtomicFact,
|
|
194
|
+
q_mean: np.ndarray | None,
|
|
195
|
+
q_var: np.ndarray | None,
|
|
196
|
+
) -> float:
|
|
197
|
+
"""Full Atkinson-Mitchell geodesic via FisherRaoMetric.
|
|
198
|
+
|
|
199
|
+
Requires fisher_mean on the fact AND query variance. If either is
|
|
200
|
+
missing, falls back to the simplified local computation so that
|
|
201
|
+
the graduated ramp still produces a score.
|
|
202
|
+
"""
|
|
203
|
+
# Need fact fisher_mean for the full metric
|
|
204
|
+
fact_mean = fact.fisher_mean
|
|
205
|
+
if fact_mean is None or len(fact_mean) != len(f_vec):
|
|
206
|
+
# No stored mean — fall back to simplified
|
|
207
|
+
return _fisher_rao_similarity(q_vec, f_vec, var_vec, self._temperature)
|
|
208
|
+
|
|
209
|
+
# Need query variance for the full metric
|
|
210
|
+
if q_mean is None or q_var is None:
|
|
211
|
+
return _fisher_rao_similarity(q_vec, f_vec, var_vec, self._temperature)
|
|
212
|
+
|
|
213
|
+
if len(q_mean) != len(fact_mean) or len(q_var) != len(var_vec):
|
|
214
|
+
return _fisher_rao_similarity(q_vec, f_vec, var_vec, self._temperature)
|
|
215
|
+
|
|
216
|
+
metric = self._get_full_metric()
|
|
217
|
+
try:
|
|
218
|
+
return metric.similarity(
|
|
219
|
+
q_mean.tolist(), q_var.tolist(),
|
|
220
|
+
list(fact_mean), list(var_vec),
|
|
221
|
+
)
|
|
222
|
+
except (ValueError, FloatingPointError):
|
|
223
|
+
# Numerical issue — fall back gracefully
|
|
224
|
+
logger.debug("Full Fisher metric raised; falling back to simplified")
|
|
225
|
+
return _fisher_rao_similarity(q_vec, f_vec, var_vec, self._temperature)
|
|
226
|
+
|
|
227
|
+
def _get_full_metric(self) -> "FisherRaoMetric": # noqa: F821
|
|
228
|
+
"""Lazy-load FisherRaoMetric to avoid import-time cost."""
|
|
229
|
+
if self._full_metric is None:
|
|
230
|
+
from superlocalmemory.math.fisher import FisherRaoMetric
|
|
231
|
+
self._full_metric = FisherRaoMetric(temperature=self._temperature)
|
|
232
|
+
return self._full_metric # type: ignore[return-value]
|
|
@@ -0,0 +1,96 @@
|
|
|
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 — Query-Adaptive Strategy.
|
|
6
|
+
|
|
7
|
+
Classifies query type and returns per-type channel weights.
|
|
8
|
+
V1 had this code (strategy_learner.py) but never wired it in.
|
|
9
|
+
|
|
10
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
11
|
+
License: MIT
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
|
|
18
|
+
STRATEGY_PRESETS: dict[str, dict[str, float]] = {
|
|
19
|
+
"temporal": {"semantic": 0.8, "bm25": 1.5, "entity_graph": 0.8, "temporal": 2.0},
|
|
20
|
+
"multi_hop": {"semantic": 1.0, "bm25": 0.8, "entity_graph": 2.0, "temporal": 0.5},
|
|
21
|
+
"aggregation": {"semantic": 1.2, "bm25": 1.5, "entity_graph": 1.0, "temporal": 0.5},
|
|
22
|
+
"opinion": {"semantic": 1.8, "bm25": 0.6, "entity_graph": 0.8, "temporal": 0.3},
|
|
23
|
+
"factual": {"semantic": 1.2, "bm25": 1.4, "entity_graph": 1.0, "temporal": 0.6},
|
|
24
|
+
"entity": {"semantic": 1.0, "bm25": 1.5, "entity_graph": 1.2, "temporal": 0.5},
|
|
25
|
+
"general": {},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_TEMPORAL_WORDS: frozenset[str] = frozenset({
|
|
29
|
+
"when", "date", "time", "year", "month", "ago", "before", "after",
|
|
30
|
+
"during", "last", "next", "recently", "earlier", "later", "since",
|
|
31
|
+
"until", "while", "between", "january", "february", "march",
|
|
32
|
+
"april", "may", "june", "july", "august", "september", "october",
|
|
33
|
+
"november", "december",
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
_MULTI_HOP_PHRASES: tuple[str, ...] = (
|
|
37
|
+
"and then", "after that", "because", "how did",
|
|
38
|
+
"as a result", "led to", "connection between", "relationship between",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
_AGGREGATION_WORDS: frozenset[str] = frozenset({
|
|
42
|
+
"all", "list", "every", "everything", "various", "different",
|
|
43
|
+
"many", "several", "multiple", "summarize", "overview",
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
_OPINION_WORDS: tuple[str, ...] = (
|
|
47
|
+
"think", "feel", "opinion", "prefer", "favorite", "best", "worst",
|
|
48
|
+
"believe", "like about", "dislike", "enjoy", "hate", "love",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class QueryStrategy:
|
|
54
|
+
"""Classified query type + adapted weights."""
|
|
55
|
+
query_type: str = "general"
|
|
56
|
+
weights: dict[str, float] = field(default_factory=dict)
|
|
57
|
+
confidence: float = 0.5
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class QueryStrategyClassifier:
|
|
61
|
+
"""Classifies queries and produces adaptive channel weights."""
|
|
62
|
+
|
|
63
|
+
def classify(self, query: str, base_weights: dict[str, float]) -> QueryStrategy:
|
|
64
|
+
"""Classify query and return adapted weights."""
|
|
65
|
+
qtype = self._detect_type(query)
|
|
66
|
+
adapted = dict(base_weights)
|
|
67
|
+
for ch, w in STRATEGY_PRESETS.get(qtype, {}).items():
|
|
68
|
+
adapted[ch] = base_weights.get(ch, 1.0) * w
|
|
69
|
+
return QueryStrategy(qtype, adapted, 0.7 if qtype != "general" else 0.5)
|
|
70
|
+
|
|
71
|
+
def _detect_type(self, query: str) -> str:
|
|
72
|
+
q = query.lower()
|
|
73
|
+
# Strip punctuation from words so "january?" matches "january"
|
|
74
|
+
words = set(re.sub(r"[^\w\s'-]", "", q).split())
|
|
75
|
+
|
|
76
|
+
# Check multi_hop BEFORE temporal — phrases like "connection between"
|
|
77
|
+
# must not be short-circuited by the word "between" in _TEMPORAL_WORDS.
|
|
78
|
+
if any(p in q for p in _MULTI_HOP_PHRASES):
|
|
79
|
+
return "multi_hop"
|
|
80
|
+
if words & _TEMPORAL_WORDS:
|
|
81
|
+
return "temporal"
|
|
82
|
+
if words & _AGGREGATION_WORDS:
|
|
83
|
+
return "aggregation"
|
|
84
|
+
if any(w in q for w in _OPINION_WORDS):
|
|
85
|
+
return "opinion"
|
|
86
|
+
# Proper nouns — exclude common sentence-initial words
|
|
87
|
+
_SENTENCE_STARTERS = {"What", "Where", "Who", "Which", "How", "When",
|
|
88
|
+
"Does", "Did", "Can", "Could", "Would", "Should",
|
|
89
|
+
"Are", "Is", "Was", "Were", "Has", "Have", "The", "Tell"}
|
|
90
|
+
proper_nouns = [m for m in re.findall(r"\b[A-Z][a-z]{1,}\b", query)
|
|
91
|
+
if m not in _SENTENCE_STARTERS]
|
|
92
|
+
if len(proper_nouns) >= 2:
|
|
93
|
+
return "entity"
|
|
94
|
+
if q.startswith(("what ", "where ", "who ", "which ", "how ")):
|
|
95
|
+
return "factual"
|
|
96
|
+
return "general"
|
|
@@ -0,0 +1,175 @@
|
|
|
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 — Temporal Retrieval Channel (3-Date Model).
|
|
6
|
+
|
|
7
|
+
Searches by referenced_date (NOT just created_at like V1).
|
|
8
|
+
Returns empty when query has no temporal signal (no recency noise).
|
|
9
|
+
|
|
10
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
11
|
+
License: MIT
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
import math
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
from dateutil.parser import parse as dateutil_parse, ParserError
|
|
21
|
+
|
|
22
|
+
from superlocalmemory.encoding.temporal_parser import TemporalParser
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from superlocalmemory.storage.database import DatabaseManager
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
_MAX_PROXIMITY_DAYS: float = 365.0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _parse_iso(s: str | None) -> datetime | None:
|
|
33
|
+
if not s:
|
|
34
|
+
return None
|
|
35
|
+
try:
|
|
36
|
+
return dateutil_parse(s)
|
|
37
|
+
except (ParserError, ValueError, OverflowError):
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _proximity_score(q: datetime, e: datetime) -> float:
|
|
42
|
+
"""Gaussian proximity: same day=1.0, 30d=0.61, 90d=0.11."""
|
|
43
|
+
dist = abs((q - e).total_seconds()) / 86400.0
|
|
44
|
+
if dist > _MAX_PROXIMITY_DAYS:
|
|
45
|
+
return 0.0
|
|
46
|
+
return math.exp(-(dist * dist) / (2.0 * 30.0 * 30.0))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TemporalChannel:
|
|
50
|
+
"""Date-aware retrieval using the 3-date temporal model."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, db: DatabaseManager) -> None:
|
|
53
|
+
self._db = db
|
|
54
|
+
|
|
55
|
+
def search(self, query: str, profile_id: str, top_k: int = 30) -> list[tuple[str, float]]:
|
|
56
|
+
"""Search for temporally relevant facts.
|
|
57
|
+
|
|
58
|
+
Two strategies:
|
|
59
|
+
1. Date proximity: scores events by date closeness to query date.
|
|
60
|
+
2. Entity-temporal: filters events by entity name in query,
|
|
61
|
+
returns ALL their temporal facts (metadata-first approach).
|
|
62
|
+
|
|
63
|
+
Returns empty only when query has no temporal signal AND no
|
|
64
|
+
entity-temporal matches.
|
|
65
|
+
"""
|
|
66
|
+
parser = TemporalParser()
|
|
67
|
+
dates = parser.extract_dates_from_text(query)
|
|
68
|
+
query_dt = _parse_iso(dates.get("referenced_date"))
|
|
69
|
+
if query_dt is None:
|
|
70
|
+
query_dt = self._try_parse(query)
|
|
71
|
+
|
|
72
|
+
# Strategy 1: Entity-temporal metadata search
|
|
73
|
+
# "When did Alice...?" → find all temporal events for Alice
|
|
74
|
+
entity_results = self._entity_temporal_search(query, profile_id)
|
|
75
|
+
|
|
76
|
+
# Strategy 2: Date proximity search
|
|
77
|
+
if query_dt is None and not entity_results:
|
|
78
|
+
return []
|
|
79
|
+
|
|
80
|
+
events = self._load_events(profile_id)
|
|
81
|
+
scored: dict[str, float] = {}
|
|
82
|
+
|
|
83
|
+
# Include entity-temporal results with high base score
|
|
84
|
+
for fid, score in entity_results:
|
|
85
|
+
scored[fid] = max(scored.get(fid, 0.0), score)
|
|
86
|
+
|
|
87
|
+
if query_dt is not None:
|
|
88
|
+
for ev in events:
|
|
89
|
+
best = 0.0
|
|
90
|
+
ref = _parse_iso(ev.get("referenced_date"))
|
|
91
|
+
if ref is not None:
|
|
92
|
+
best = max(best, _proximity_score(query_dt, ref))
|
|
93
|
+
|
|
94
|
+
obs = _parse_iso(ev.get("observation_date"))
|
|
95
|
+
if obs is not None:
|
|
96
|
+
best = max(best, _proximity_score(query_dt, obs) * 0.8)
|
|
97
|
+
|
|
98
|
+
i_start = _parse_iso(ev.get("interval_start"))
|
|
99
|
+
i_end = _parse_iso(ev.get("interval_end"))
|
|
100
|
+
if i_start and i_end:
|
|
101
|
+
if i_start <= query_dt <= i_end:
|
|
102
|
+
best = max(best, 1.0)
|
|
103
|
+
else:
|
|
104
|
+
best = max(best, max(
|
|
105
|
+
_proximity_score(query_dt, i_start),
|
|
106
|
+
_proximity_score(query_dt, i_end),
|
|
107
|
+
) * 0.9)
|
|
108
|
+
|
|
109
|
+
if best > 0.0:
|
|
110
|
+
fid = ev["fact_id"]
|
|
111
|
+
scored[fid] = max(scored.get(fid, 0.0), best)
|
|
112
|
+
|
|
113
|
+
results = sorted(scored.items(), key=lambda x: x[1], reverse=True)
|
|
114
|
+
return results[:top_k]
|
|
115
|
+
|
|
116
|
+
def _entity_temporal_search(
|
|
117
|
+
self, query: str, profile_id: str,
|
|
118
|
+
) -> list[tuple[str, float]]:
|
|
119
|
+
"""Metadata-first: find temporal events for entities mentioned in query.
|
|
120
|
+
|
|
121
|
+
"When did Alice do X?" → SQL filter by entity_id for Alice → return
|
|
122
|
+
all temporal facts about Alice. High precision for entity+time queries.
|
|
123
|
+
"""
|
|
124
|
+
import re
|
|
125
|
+
_PROPER_RE = re.compile(r"\b([A-Z][a-z]+)\b")
|
|
126
|
+
names = [m.group(1) for m in _PROPER_RE.finditer(query)]
|
|
127
|
+
if not names:
|
|
128
|
+
return []
|
|
129
|
+
|
|
130
|
+
results: list[tuple[str, float]] = []
|
|
131
|
+
seen: set[str] = set()
|
|
132
|
+
|
|
133
|
+
for name in names[:3]: # Limit to first 3 entity mentions
|
|
134
|
+
# Look up entity ID
|
|
135
|
+
entity = self._db.get_entity_by_name(name, profile_id)
|
|
136
|
+
if entity is None:
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
# Find all temporal events for this entity
|
|
140
|
+
rows = self._db.execute(
|
|
141
|
+
"SELECT fact_id FROM temporal_events "
|
|
142
|
+
"WHERE profile_id = ? AND entity_id = ?",
|
|
143
|
+
(profile_id, entity.entity_id),
|
|
144
|
+
)
|
|
145
|
+
for row in rows:
|
|
146
|
+
fid = dict(row)["fact_id"]
|
|
147
|
+
if fid not in seen:
|
|
148
|
+
seen.add(fid)
|
|
149
|
+
results.append((fid, 0.85)) # High base score for entity-temporal
|
|
150
|
+
|
|
151
|
+
return results
|
|
152
|
+
|
|
153
|
+
def _load_events(self, profile_id: str) -> list[dict]:
|
|
154
|
+
rows = self._db.execute(
|
|
155
|
+
"SELECT fact_id, observation_date, referenced_date, "
|
|
156
|
+
"interval_start, interval_end "
|
|
157
|
+
"FROM temporal_events WHERE profile_id = ?",
|
|
158
|
+
(profile_id,),
|
|
159
|
+
)
|
|
160
|
+
return [dict(r) for r in rows]
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def _try_parse(text: str) -> datetime | None:
|
|
164
|
+
"""Fuzzy date parse with safety guards.
|
|
165
|
+
|
|
166
|
+
dateutil fuzzy=True is exponential on long non-date text.
|
|
167
|
+
Guard: only attempt on short strings (< 60 chars) that contain
|
|
168
|
+
at least one digit (dates always have numbers).
|
|
169
|
+
"""
|
|
170
|
+
if len(text) > 60 or not any(c.isdigit() for c in text):
|
|
171
|
+
return None
|
|
172
|
+
try:
|
|
173
|
+
return dateutil_parse(text, fuzzy=True)
|
|
174
|
+
except (ParserError, ValueError, OverflowError):
|
|
175
|
+
return None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Server package — API and UI servers
|