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,35 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Tests for auto-retrain mechanism when feature dimensions change 12->20.
|
|
4
|
-
"""
|
|
5
|
-
import sys
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class TestAutoRetrain:
|
|
11
|
-
def test_feature_dimension_is_20(self):
|
|
12
|
-
from learning.feature_extractor import NUM_FEATURES
|
|
13
|
-
assert NUM_FEATURES == 20
|
|
14
|
-
|
|
15
|
-
def test_ranker_handles_dimension_mismatch(self):
|
|
16
|
-
"""Ranker should not crash when loaded model has different dimensions."""
|
|
17
|
-
from learning.adaptive_ranker import AdaptiveRanker
|
|
18
|
-
ranker = AdaptiveRanker()
|
|
19
|
-
# Even without a trained model, phase 1 should work
|
|
20
|
-
results = [{'id': 1, 'content': 'test', 'score': 0.5, 'match_type': 'semantic', 'importance': 5}]
|
|
21
|
-
ranked = ranker.rerank(results, "test")
|
|
22
|
-
assert len(ranked) == 1
|
|
23
|
-
|
|
24
|
-
def test_phase1_immediate_during_retrain(self):
|
|
25
|
-
"""Phase 1 (rule-based) should work immediately even during model retrain."""
|
|
26
|
-
from learning.adaptive_ranker import AdaptiveRanker
|
|
27
|
-
ranker = AdaptiveRanker()
|
|
28
|
-
results = [
|
|
29
|
-
{'id': 1, 'content': 'memory A', 'score': 0.9, 'match_type': 'semantic', 'importance': 8},
|
|
30
|
-
{'id': 2, 'content': 'memory B', 'score': 0.3, 'match_type': 'keyword', 'importance': 2},
|
|
31
|
-
]
|
|
32
|
-
ranked = ranker.rerank(results, "test query")
|
|
33
|
-
assert len(ranked) == 2
|
|
34
|
-
# Higher base score should still rank first in phase 1
|
|
35
|
-
assert ranked[0]['score'] >= ranked[1]['score']
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""End-to-end tests for adaptive ranking with 20-feature vector.
|
|
4
|
-
"""
|
|
5
|
-
import sys
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class TestE2ERankingV28:
|
|
11
|
-
def test_full_20_feature_pipeline(self):
|
|
12
|
-
"""Full pipeline: extract 20 features -> rank results."""
|
|
13
|
-
from learning.feature_extractor import FeatureExtractor
|
|
14
|
-
from learning.adaptive_ranker import AdaptiveRanker
|
|
15
|
-
|
|
16
|
-
extractor = FeatureExtractor()
|
|
17
|
-
ranker = AdaptiveRanker()
|
|
18
|
-
|
|
19
|
-
memories = [
|
|
20
|
-
{'id': 1, 'content': 'Python best practices for production', 'score': 0.8,
|
|
21
|
-
'match_type': 'semantic', 'importance': 8, 'lifecycle_state': 'active',
|
|
22
|
-
'access_count': 15, 'created_at': '2026-02-01'},
|
|
23
|
-
{'id': 2, 'content': 'Old JavaScript patterns deprecated', 'score': 0.75,
|
|
24
|
-
'match_type': 'semantic', 'importance': 3, 'lifecycle_state': 'cold',
|
|
25
|
-
'access_count': 1, 'created_at': '2025-06-01'},
|
|
26
|
-
]
|
|
27
|
-
|
|
28
|
-
# Extract features
|
|
29
|
-
for mem in memories:
|
|
30
|
-
features = extractor.extract_features(mem, "Python production")
|
|
31
|
-
assert len(features) == 20
|
|
32
|
-
|
|
33
|
-
# Rank
|
|
34
|
-
ranked = ranker.rerank(memories, "Python production")
|
|
35
|
-
assert len(ranked) == 2
|
|
36
|
-
|
|
37
|
-
def test_lifecycle_affects_ranking(self):
|
|
38
|
-
"""Active memories should generally rank above cold ones."""
|
|
39
|
-
from learning.adaptive_ranker import AdaptiveRanker
|
|
40
|
-
ranker = AdaptiveRanker()
|
|
41
|
-
|
|
42
|
-
results = [
|
|
43
|
-
{'id': 1, 'content': 'cold memory', 'score': 0.85, 'match_type': 'semantic',
|
|
44
|
-
'importance': 5, 'lifecycle_state': 'cold'},
|
|
45
|
-
{'id': 2, 'content': 'active memory', 'score': 0.80, 'match_type': 'semantic',
|
|
46
|
-
'importance': 5, 'lifecycle_state': 'active'},
|
|
47
|
-
]
|
|
48
|
-
ranked = ranker.rerank(results, "test")
|
|
49
|
-
# Even with slightly lower base score, active should win due to lifecycle boost
|
|
50
|
-
assert ranked[0]['id'] == 2
|
|
51
|
-
|
|
52
|
-
def test_high_outcome_success_boosts_ranking(self):
|
|
53
|
-
"""Memories with high success rates should rank higher."""
|
|
54
|
-
from learning.adaptive_ranker import AdaptiveRanker
|
|
55
|
-
ranker = AdaptiveRanker()
|
|
56
|
-
|
|
57
|
-
results = [
|
|
58
|
-
{'id': 1, 'content': 'frequently failing', 'score': 0.8, 'match_type': 'semantic',
|
|
59
|
-
'importance': 5, 'outcome_success_rate': 0.1},
|
|
60
|
-
{'id': 2, 'content': 'consistently useful', 'score': 0.8, 'match_type': 'semantic',
|
|
61
|
-
'importance': 5, 'outcome_success_rate': 0.95},
|
|
62
|
-
]
|
|
63
|
-
ranked = ranker.rerank(results, "test")
|
|
64
|
-
assert ranked[0]['id'] == 2
|
|
65
|
-
|
|
66
|
-
def test_result_ordering_reflects_all_signals(self):
|
|
67
|
-
"""Result ordering should reflect lifecycle + behavioral + base score."""
|
|
68
|
-
from learning.adaptive_ranker import AdaptiveRanker
|
|
69
|
-
ranker = AdaptiveRanker()
|
|
70
|
-
|
|
71
|
-
results = [
|
|
72
|
-
{'id': 1, 'content': 'A', 'score': 0.7, 'match_type': 'semantic', 'importance': 5,
|
|
73
|
-
'lifecycle_state': 'active', 'outcome_success_rate': 0.9},
|
|
74
|
-
{'id': 2, 'content': 'B', 'score': 0.9, 'match_type': 'semantic', 'importance': 5,
|
|
75
|
-
'lifecycle_state': 'cold', 'outcome_success_rate': 0.1},
|
|
76
|
-
{'id': 3, 'content': 'C', 'score': 0.8, 'match_type': 'semantic', 'importance': 8,
|
|
77
|
-
'lifecycle_state': 'active', 'outcome_success_rate': 0.5},
|
|
78
|
-
]
|
|
79
|
-
ranked = ranker.rerank(results, "test")
|
|
80
|
-
assert len(ranked) == 3
|
|
81
|
-
# All should have final scores
|
|
82
|
-
assert all('score' in r for r in ranked)
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Tests for v2.8 feature extractor — 20-dimensional feature vectors.
|
|
4
|
-
"""
|
|
5
|
-
import sys
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class TestFeatureExtractorV28:
|
|
11
|
-
def test_feature_count_is_20(self):
|
|
12
|
-
from learning.feature_extractor import FEATURE_NAMES
|
|
13
|
-
assert len(FEATURE_NAMES) == 20
|
|
14
|
-
|
|
15
|
-
def test_new_feature_names_present(self):
|
|
16
|
-
from learning.feature_extractor import FEATURE_NAMES
|
|
17
|
-
new_features = ['lifecycle_state', 'outcome_success_rate', 'outcome_count',
|
|
18
|
-
'behavioral_match', 'cross_project_score', 'retention_priority',
|
|
19
|
-
'trust_at_creation', 'lifecycle_aware_decay']
|
|
20
|
-
for f in new_features:
|
|
21
|
-
assert f in FEATURE_NAMES
|
|
22
|
-
|
|
23
|
-
def test_extract_returns_20_features(self):
|
|
24
|
-
from learning.feature_extractor import FeatureExtractor
|
|
25
|
-
extractor = FeatureExtractor()
|
|
26
|
-
memory = {
|
|
27
|
-
'id': 1, 'content': 'test memory about Python', 'importance': 5,
|
|
28
|
-
'created_at': '2026-02-20T10:00:00', 'last_accessed': '2026-02-25T10:00:00',
|
|
29
|
-
'access_count': 3, 'tags': ['python'], 'project_name': 'myproject',
|
|
30
|
-
'lifecycle_state': 'active', 'score': 0.8, 'match_type': 'semantic',
|
|
31
|
-
}
|
|
32
|
-
features = extractor.extract_features(memory, "Python programming")
|
|
33
|
-
assert len(features) == 20
|
|
34
|
-
assert all(isinstance(f, (int, float)) for f in features)
|
|
35
|
-
|
|
36
|
-
def test_lifecycle_state_encoding(self):
|
|
37
|
-
from learning.feature_extractor import FeatureExtractor
|
|
38
|
-
extractor = FeatureExtractor()
|
|
39
|
-
base = {'id': 1, 'content': 'test', 'importance': 5, 'created_at': '2026-02-20',
|
|
40
|
-
'access_count': 0, 'score': 0.5, 'match_type': 'semantic'}
|
|
41
|
-
|
|
42
|
-
active = extractor.extract_features({**base, 'lifecycle_state': 'active'}, "test")
|
|
43
|
-
warm = extractor.extract_features({**base, 'lifecycle_state': 'warm'}, "test")
|
|
44
|
-
cold = extractor.extract_features({**base, 'lifecycle_state': 'cold'}, "test")
|
|
45
|
-
|
|
46
|
-
assert active[12] > warm[12] > cold[12]
|
|
47
|
-
|
|
48
|
-
def test_default_values_when_data_missing(self):
|
|
49
|
-
"""Features should return sensible defaults when data is unavailable."""
|
|
50
|
-
from learning.feature_extractor import FeatureExtractor
|
|
51
|
-
extractor = FeatureExtractor()
|
|
52
|
-
minimal = {'id': 1, 'content': 'test', 'importance': 5, 'score': 0.5, 'match_type': 'semantic'}
|
|
53
|
-
features = extractor.extract_features(minimal, "test")
|
|
54
|
-
assert len(features) == 20
|
|
55
|
-
# lifecycle_state default (active) = 1.0
|
|
56
|
-
assert features[12] == 1.0
|
|
57
|
-
# outcome_success_rate default = 0.5
|
|
58
|
-
assert features[13] == 0.5
|
|
59
|
-
# retention_priority default = 0.5
|
|
60
|
-
assert features[17] == 0.5
|
|
61
|
-
# trust_at_creation default = 0.8
|
|
62
|
-
assert features[18] == 0.8
|
|
63
|
-
|
|
64
|
-
def test_backward_compat_old_12_features_still_work(self):
|
|
65
|
-
"""The first 12 features should still be computed correctly."""
|
|
66
|
-
from learning.feature_extractor import FeatureExtractor
|
|
67
|
-
extractor = FeatureExtractor()
|
|
68
|
-
memory = {'id': 1, 'content': 'Python is great', 'importance': 8,
|
|
69
|
-
'created_at': '2026-02-20', 'access_count': 5, 'score': 0.9,
|
|
70
|
-
'match_type': 'semantic', 'lifecycle_state': 'active'}
|
|
71
|
-
features = extractor.extract_features(memory, "Python")
|
|
72
|
-
# importance_norm (index 6) should be 0.8 (8/10)
|
|
73
|
-
assert abs(features[6] - 0.8) < 0.01
|
|
74
|
-
|
|
75
|
-
def test_extract_batch_returns_20_wide(self):
|
|
76
|
-
from learning.feature_extractor import FeatureExtractor
|
|
77
|
-
extractor = FeatureExtractor()
|
|
78
|
-
memories = [
|
|
79
|
-
{'id': 1, 'content': 'mem1', 'importance': 5, 'score': 0.5, 'match_type': 'semantic'},
|
|
80
|
-
{'id': 2, 'content': 'mem2', 'importance': 7, 'score': 0.7, 'match_type': 'semantic'},
|
|
81
|
-
]
|
|
82
|
-
batch = extractor.extract_batch(memories, "test")
|
|
83
|
-
assert len(batch) == 2
|
|
84
|
-
assert all(len(v) == 20 for v in batch)
|
|
85
|
-
|
|
86
|
-
def test_outcome_success_rate_from_memory(self):
|
|
87
|
-
"""If memory has outcome_success_rate in dict, use it."""
|
|
88
|
-
from learning.feature_extractor import FeatureExtractor
|
|
89
|
-
extractor = FeatureExtractor()
|
|
90
|
-
memory = {'id': 1, 'content': 'test', 'importance': 5, 'score': 0.5,
|
|
91
|
-
'match_type': 'semantic', 'outcome_success_rate': 0.85}
|
|
92
|
-
features = extractor.extract_features(memory, "test")
|
|
93
|
-
assert abs(features[13] - 0.85) < 0.01
|
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
4
|
-
import hashlib
|
|
5
|
-
import time
|
|
6
|
-
|
|
7
|
-
import pytest
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
# ---------------------------------------------------------------------------
|
|
11
|
-
# Fixtures
|
|
12
|
-
# ---------------------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
@pytest.fixture(autouse=True)
|
|
15
|
-
def reset_singleton():
|
|
16
|
-
from src.learning.learning_db import LearningDB
|
|
17
|
-
LearningDB.reset_instance()
|
|
18
|
-
yield
|
|
19
|
-
LearningDB.reset_instance()
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@pytest.fixture
|
|
23
|
-
def learning_db(tmp_path):
|
|
24
|
-
from src.learning.learning_db import LearningDB
|
|
25
|
-
db_path = tmp_path / "learning.db"
|
|
26
|
-
return LearningDB(db_path=db_path)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@pytest.fixture
|
|
30
|
-
def collector(learning_db):
|
|
31
|
-
from src.learning.feedback_collector import FeedbackCollector
|
|
32
|
-
return FeedbackCollector(learning_db=learning_db)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@pytest.fixture
|
|
36
|
-
def collector_no_db():
|
|
37
|
-
"""Collector with no database — tests graceful degradation."""
|
|
38
|
-
from src.learning.feedback_collector import FeedbackCollector
|
|
39
|
-
return FeedbackCollector(learning_db=None)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# ---------------------------------------------------------------------------
|
|
43
|
-
# Channel 1: MCP memory_used
|
|
44
|
-
# ---------------------------------------------------------------------------
|
|
45
|
-
|
|
46
|
-
class TestRecordMemoryUsed:
|
|
47
|
-
def test_high_usefulness(self, collector, learning_db):
|
|
48
|
-
row_id = collector.record_memory_used(42, "deploy fastapi", usefulness="high")
|
|
49
|
-
assert row_id is not None
|
|
50
|
-
|
|
51
|
-
rows = learning_db.get_feedback_for_training()
|
|
52
|
-
assert len(rows) == 1
|
|
53
|
-
assert rows[0]["signal_type"] == "mcp_used_high"
|
|
54
|
-
assert rows[0]["signal_value"] == 1.0
|
|
55
|
-
assert rows[0]["channel"] == "mcp"
|
|
56
|
-
|
|
57
|
-
def test_medium_usefulness(self, collector, learning_db):
|
|
58
|
-
collector.record_memory_used(42, "deploy fastapi", usefulness="medium")
|
|
59
|
-
rows = learning_db.get_feedback_for_training()
|
|
60
|
-
assert rows[0]["signal_type"] == "mcp_used_medium"
|
|
61
|
-
assert rows[0]["signal_value"] == 0.7
|
|
62
|
-
|
|
63
|
-
def test_low_usefulness(self, collector, learning_db):
|
|
64
|
-
collector.record_memory_used(42, "deploy fastapi", usefulness="low")
|
|
65
|
-
rows = learning_db.get_feedback_for_training()
|
|
66
|
-
assert rows[0]["signal_type"] == "mcp_used_low"
|
|
67
|
-
assert rows[0]["signal_value"] == 0.4
|
|
68
|
-
|
|
69
|
-
def test_invalid_usefulness_defaults_to_high(self, collector, learning_db):
|
|
70
|
-
collector.record_memory_used(42, "test query", usefulness="INVALID")
|
|
71
|
-
rows = learning_db.get_feedback_for_training()
|
|
72
|
-
assert rows[0]["signal_type"] == "mcp_used_high"
|
|
73
|
-
|
|
74
|
-
def test_empty_query_returns_none(self, collector):
|
|
75
|
-
result = collector.record_memory_used(42, "")
|
|
76
|
-
assert result is None
|
|
77
|
-
|
|
78
|
-
def test_source_tool_recorded(self, collector, learning_db):
|
|
79
|
-
collector.record_memory_used(
|
|
80
|
-
42, "test query", source_tool="claude-desktop", rank_position=2,
|
|
81
|
-
)
|
|
82
|
-
rows = learning_db.get_feedback_for_training()
|
|
83
|
-
assert rows[0]["source_tool"] == "claude-desktop"
|
|
84
|
-
assert rows[0]["rank_position"] == 2
|
|
85
|
-
|
|
86
|
-
def test_no_db_auto_creates(self, collector_no_db):
|
|
87
|
-
"""v2.7.2+: FeedbackCollector auto-creates LearningDB when None passed."""
|
|
88
|
-
result = collector_no_db.record_memory_used(42, "test query")
|
|
89
|
-
# Auto-created DB means this succeeds (returns row ID)
|
|
90
|
-
assert result is not None
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
# ---------------------------------------------------------------------------
|
|
94
|
-
# Channel 2: CLI slm useful
|
|
95
|
-
# ---------------------------------------------------------------------------
|
|
96
|
-
|
|
97
|
-
class TestRecordCliUseful:
|
|
98
|
-
def test_batch_ids(self, collector, learning_db):
|
|
99
|
-
row_ids = collector.record_cli_useful([10, 20, 30], "deploy fastapi")
|
|
100
|
-
assert len(row_ids) == 3
|
|
101
|
-
assert all(rid is not None for rid in row_ids)
|
|
102
|
-
assert learning_db.get_feedback_count() == 3
|
|
103
|
-
|
|
104
|
-
def test_signal_value(self, collector, learning_db):
|
|
105
|
-
collector.record_cli_useful([42], "query")
|
|
106
|
-
rows = learning_db.get_feedback_for_training()
|
|
107
|
-
assert rows[0]["signal_value"] == 0.9
|
|
108
|
-
assert rows[0]["signal_type"] == "cli_useful"
|
|
109
|
-
assert rows[0]["channel"] == "cli"
|
|
110
|
-
|
|
111
|
-
def test_all_share_same_query_hash(self, collector, learning_db):
|
|
112
|
-
collector.record_cli_useful([1, 2, 3], "same query")
|
|
113
|
-
rows = learning_db.get_feedback_for_training()
|
|
114
|
-
hashes = {r["query_hash"] for r in rows}
|
|
115
|
-
assert len(hashes) == 1
|
|
116
|
-
|
|
117
|
-
def test_empty_query(self, collector):
|
|
118
|
-
result = collector.record_cli_useful([1, 2], "")
|
|
119
|
-
assert result == [None, None]
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
# ---------------------------------------------------------------------------
|
|
123
|
-
# Channel 3: Dashboard click
|
|
124
|
-
# ---------------------------------------------------------------------------
|
|
125
|
-
|
|
126
|
-
class TestRecordDashboardClick:
|
|
127
|
-
def test_basic_click(self, collector, learning_db):
|
|
128
|
-
row_id = collector.record_dashboard_click(42, "test query")
|
|
129
|
-
assert row_id is not None
|
|
130
|
-
rows = learning_db.get_feedback_for_training()
|
|
131
|
-
assert rows[0]["signal_type"] == "dashboard_click"
|
|
132
|
-
assert rows[0]["signal_value"] == 0.8
|
|
133
|
-
assert rows[0]["channel"] == "dashboard"
|
|
134
|
-
|
|
135
|
-
def test_with_dwell_time(self, collector, learning_db):
|
|
136
|
-
collector.record_dashboard_click(42, "test query", dwell_time=15.3)
|
|
137
|
-
# dwell_time is stored in ranking_feedback but not in training export
|
|
138
|
-
# Verify via direct DB query
|
|
139
|
-
conn = learning_db._get_connection()
|
|
140
|
-
cursor = conn.cursor()
|
|
141
|
-
cursor.execute("SELECT dwell_time FROM ranking_feedback WHERE memory_id = 42")
|
|
142
|
-
row = cursor.fetchone()
|
|
143
|
-
conn.close()
|
|
144
|
-
assert row is not None
|
|
145
|
-
assert abs(row[0] - 15.3) < 0.01
|
|
146
|
-
|
|
147
|
-
def test_empty_query_returns_none(self, collector):
|
|
148
|
-
result = collector.record_dashboard_click(42, "")
|
|
149
|
-
assert result is None
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
# ---------------------------------------------------------------------------
|
|
153
|
-
# Query Hashing
|
|
154
|
-
# ---------------------------------------------------------------------------
|
|
155
|
-
|
|
156
|
-
class TestHashQuery:
|
|
157
|
-
def test_deterministic(self, collector):
|
|
158
|
-
h1 = collector._hash_query("deploy fastapi")
|
|
159
|
-
h2 = collector._hash_query("deploy fastapi")
|
|
160
|
-
assert h1 == h2
|
|
161
|
-
|
|
162
|
-
def test_sha256_first_16(self, collector):
|
|
163
|
-
query = "deploy fastapi"
|
|
164
|
-
expected = hashlib.sha256(query.encode("utf-8")).hexdigest()[:16]
|
|
165
|
-
assert collector._hash_query(query) == expected
|
|
166
|
-
|
|
167
|
-
def test_length_is_16(self, collector):
|
|
168
|
-
result = collector._hash_query("any string")
|
|
169
|
-
assert len(result) == 16
|
|
170
|
-
|
|
171
|
-
def test_different_queries_different_hashes(self, collector):
|
|
172
|
-
h1 = collector._hash_query("query one")
|
|
173
|
-
h2 = collector._hash_query("query two")
|
|
174
|
-
assert h1 != h2
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
# ---------------------------------------------------------------------------
|
|
178
|
-
# Keyword Extraction
|
|
179
|
-
# ---------------------------------------------------------------------------
|
|
180
|
-
|
|
181
|
-
class TestExtractKeywords:
|
|
182
|
-
def test_stopword_removal(self, collector):
|
|
183
|
-
kw = collector._extract_keywords("how to deploy the app")
|
|
184
|
-
assert "how" not in kw
|
|
185
|
-
assert "to" not in kw
|
|
186
|
-
assert "the" not in kw
|
|
187
|
-
assert "deploy" in kw
|
|
188
|
-
|
|
189
|
-
def test_top_n_limit(self, collector):
|
|
190
|
-
kw = collector._extract_keywords(
|
|
191
|
-
"python fastapi docker kubernetes deployment pipeline"
|
|
192
|
-
)
|
|
193
|
-
assert len(kw.split(",")) <= 3
|
|
194
|
-
|
|
195
|
-
def test_empty_query(self, collector):
|
|
196
|
-
assert collector._extract_keywords("") == ""
|
|
197
|
-
|
|
198
|
-
def test_only_stopwords(self, collector):
|
|
199
|
-
assert collector._extract_keywords("the and or but") == ""
|
|
200
|
-
|
|
201
|
-
def test_comma_separated_output(self, collector):
|
|
202
|
-
kw = collector._extract_keywords("deploy fastapi docker")
|
|
203
|
-
assert "," in kw or len(kw.split(",")) == 1 # single keyword = no comma
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
# ---------------------------------------------------------------------------
|
|
207
|
-
# Passive Decay
|
|
208
|
-
# ---------------------------------------------------------------------------
|
|
209
|
-
|
|
210
|
-
class TestPassiveDecay:
|
|
211
|
-
def test_record_recall_results(self, collector):
|
|
212
|
-
collector.record_recall_results("test query", [1, 2, 3])
|
|
213
|
-
assert collector._recall_count == 1
|
|
214
|
-
|
|
215
|
-
def test_compute_passive_decay_below_threshold(self, collector):
|
|
216
|
-
"""Should return 0 if below threshold."""
|
|
217
|
-
collector.record_recall_results("test query", [1, 2, 3])
|
|
218
|
-
result = collector.compute_passive_decay(threshold=10)
|
|
219
|
-
assert result == 0
|
|
220
|
-
|
|
221
|
-
def test_compute_passive_decay_with_candidates(self, collector, learning_db):
|
|
222
|
-
"""Memory appearing in 5+ distinct queries should get decay signal."""
|
|
223
|
-
# Create 10+ recall operations with memory_id=99 appearing in 6 distinct queries
|
|
224
|
-
for i in range(10):
|
|
225
|
-
collector.record_recall_results(f"query_{i}", [99, 100 + i])
|
|
226
|
-
|
|
227
|
-
decay_count = collector.compute_passive_decay(threshold=10)
|
|
228
|
-
# memory 99 appeared in 10 distinct queries, no positive feedback
|
|
229
|
-
assert decay_count >= 1
|
|
230
|
-
|
|
231
|
-
def test_no_decay_for_positively_rated(self, collector, learning_db):
|
|
232
|
-
"""Memories with positive feedback should NOT get passive decay."""
|
|
233
|
-
# Give memory 99 positive feedback first
|
|
234
|
-
learning_db.store_feedback(
|
|
235
|
-
query_hash="q", memory_id=99, signal_type="mcp_used",
|
|
236
|
-
signal_value=1.0, channel="mcp",
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
# Record 10+ recall operations with memory 99
|
|
240
|
-
for i in range(12):
|
|
241
|
-
collector.record_recall_results(f"query_{i}", [99])
|
|
242
|
-
|
|
243
|
-
decay_count = collector.compute_passive_decay(threshold=10)
|
|
244
|
-
assert decay_count == 0
|
|
245
|
-
|
|
246
|
-
def test_buffer_cleared_after_decay(self, collector):
|
|
247
|
-
"""Recall buffer should be cleared after computing decay."""
|
|
248
|
-
for i in range(10):
|
|
249
|
-
collector.record_recall_results(f"q{i}", [1])
|
|
250
|
-
collector.compute_passive_decay(threshold=10)
|
|
251
|
-
assert collector._recall_count == 0
|
|
252
|
-
|
|
253
|
-
def test_empty_query_ignored(self, collector):
|
|
254
|
-
collector.record_recall_results("", [1, 2, 3])
|
|
255
|
-
assert collector._recall_count == 0
|
|
256
|
-
|
|
257
|
-
def test_empty_ids_ignored(self, collector):
|
|
258
|
-
collector.record_recall_results("test query", [])
|
|
259
|
-
assert collector._recall_count == 0
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
# ---------------------------------------------------------------------------
|
|
263
|
-
# Summary
|
|
264
|
-
# ---------------------------------------------------------------------------
|
|
265
|
-
|
|
266
|
-
class TestFeedbackSummary:
|
|
267
|
-
def test_summary_with_data(self, collector, learning_db):
|
|
268
|
-
collector.record_memory_used(1, "q1", usefulness="high")
|
|
269
|
-
collector.record_cli_useful([2], "q2")
|
|
270
|
-
collector.record_dashboard_click(3, "q3")
|
|
271
|
-
|
|
272
|
-
summary = collector.get_feedback_summary()
|
|
273
|
-
assert summary["total_signals"] == 3
|
|
274
|
-
assert summary["unique_queries"] == 3
|
|
275
|
-
assert "mcp" in summary["by_channel"]
|
|
276
|
-
assert "cli" in summary["by_channel"]
|
|
277
|
-
assert "dashboard" in summary["by_channel"]
|
|
278
|
-
|
|
279
|
-
def test_summary_empty_db(self, collector):
|
|
280
|
-
summary = collector.get_feedback_summary()
|
|
281
|
-
assert summary["total_signals"] == 0
|
|
282
|
-
assert summary["unique_queries"] == 0
|
|
283
|
-
|
|
284
|
-
def test_summary_no_db_auto_creates(self, collector_no_db):
|
|
285
|
-
"""v2.7.2+: Auto-created DB returns valid summary, not error."""
|
|
286
|
-
summary = collector_no_db.get_feedback_summary()
|
|
287
|
-
assert "total_signals" in summary
|
|
288
|
-
|
|
289
|
-
def test_summary_buffer_stats(self, collector):
|
|
290
|
-
collector.record_recall_results("q1", [1, 2, 3])
|
|
291
|
-
collector.record_recall_results("q2", [1, 4, 5])
|
|
292
|
-
|
|
293
|
-
summary = collector.get_feedback_summary()
|
|
294
|
-
assert summary["recall_buffer_size"] == 2
|