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.
Files changed (431) hide show
  1. package/LICENSE +9 -1
  2. package/NOTICE +63 -0
  3. package/README.md +165 -480
  4. package/bin/slm +17 -449
  5. package/bin/slm-npm +1 -1
  6. package/conftest.py +5 -0
  7. package/docs/api-reference.md +284 -0
  8. package/docs/architecture.md +149 -0
  9. package/docs/auto-memory.md +150 -0
  10. package/docs/cli-reference.md +276 -0
  11. package/docs/compliance.md +191 -0
  12. package/docs/configuration.md +182 -0
  13. package/docs/getting-started.md +102 -0
  14. package/docs/ide-setup.md +261 -0
  15. package/docs/mcp-tools.md +220 -0
  16. package/docs/migration-from-v2.md +170 -0
  17. package/docs/profiles.md +173 -0
  18. package/docs/troubleshooting.md +310 -0
  19. package/{configs → ide/configs}/antigravity-mcp.json +3 -3
  20. package/ide/configs/chatgpt-desktop-mcp.json +16 -0
  21. package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
  22. package/{configs → ide/configs}/codex-mcp.toml +4 -4
  23. package/{configs → ide/configs}/continue-mcp.yaml +4 -3
  24. package/{configs → ide/configs}/continue-skills.yaml +6 -6
  25. package/ide/configs/cursor-mcp.json +15 -0
  26. package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
  27. package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
  28. package/{configs → ide/configs}/opencode-mcp.json +2 -2
  29. package/{configs → ide/configs}/perplexity-mcp.json +2 -2
  30. package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
  31. package/{configs → ide/configs}/windsurf-mcp.json +3 -3
  32. package/{configs → ide/configs}/zed-mcp.json +2 -2
  33. package/{hooks → ide/hooks}/context-hook.js +9 -20
  34. package/ide/hooks/memory-list-skill.js +70 -0
  35. package/ide/hooks/memory-profile-skill.js +101 -0
  36. package/ide/hooks/memory-recall-skill.js +62 -0
  37. package/ide/hooks/memory-remember-skill.js +68 -0
  38. package/ide/hooks/memory-reset-skill.js +160 -0
  39. package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
  40. package/ide/integrations/langchain/README.md +106 -0
  41. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
  42. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
  43. package/ide/integrations/langchain/pyproject.toml +38 -0
  44. package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
  45. package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
  46. package/ide/integrations/langchain/tests/test_security.py +117 -0
  47. package/ide/integrations/llamaindex/README.md +81 -0
  48. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
  49. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
  50. package/ide/integrations/llamaindex/pyproject.toml +43 -0
  51. package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
  52. package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
  53. package/ide/integrations/llamaindex/tests/test_security.py +241 -0
  54. package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
  55. package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
  56. package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
  57. package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
  58. package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
  59. package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
  60. package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
  61. package/package.json +13 -22
  62. package/pyproject.toml +85 -0
  63. package/scripts/build-dmg.sh +417 -0
  64. package/scripts/install-skills.ps1 +334 -0
  65. package/scripts/postinstall.js +2 -2
  66. package/scripts/start-dashboard.ps1 +52 -0
  67. package/scripts/start-dashboard.sh +41 -0
  68. package/scripts/sync-wiki.ps1 +127 -0
  69. package/scripts/sync-wiki.sh +82 -0
  70. package/scripts/test-dmg.sh +161 -0
  71. package/scripts/test-npm-package.ps1 +252 -0
  72. package/scripts/test-npm-package.sh +207 -0
  73. package/scripts/verify-install.ps1 +294 -0
  74. package/scripts/verify-install.sh +266 -0
  75. package/src/superlocalmemory/__init__.py +0 -0
  76. package/src/superlocalmemory/attribution/__init__.py +9 -0
  77. package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
  78. package/src/superlocalmemory/attribution/signer.py +153 -0
  79. package/src/superlocalmemory/attribution/watermark.py +189 -0
  80. package/src/superlocalmemory/cli/__init__.py +5 -0
  81. package/src/superlocalmemory/cli/commands.py +245 -0
  82. package/src/superlocalmemory/cli/main.py +89 -0
  83. package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
  84. package/src/superlocalmemory/cli/post_install.py +99 -0
  85. package/src/superlocalmemory/cli/setup_wizard.py +129 -0
  86. package/src/superlocalmemory/compliance/__init__.py +0 -0
  87. package/src/superlocalmemory/compliance/abac.py +204 -0
  88. package/src/superlocalmemory/compliance/audit.py +314 -0
  89. package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
  90. package/src/superlocalmemory/compliance/gdpr.py +294 -0
  91. package/src/superlocalmemory/compliance/lifecycle.py +158 -0
  92. package/src/superlocalmemory/compliance/retention.py +232 -0
  93. package/src/superlocalmemory/compliance/scheduler.py +148 -0
  94. package/src/superlocalmemory/core/__init__.py +0 -0
  95. package/src/superlocalmemory/core/config.py +391 -0
  96. package/src/superlocalmemory/core/embeddings.py +293 -0
  97. package/src/superlocalmemory/core/engine.py +701 -0
  98. package/src/superlocalmemory/core/hooks.py +65 -0
  99. package/src/superlocalmemory/core/maintenance.py +172 -0
  100. package/src/superlocalmemory/core/modes.py +140 -0
  101. package/src/superlocalmemory/core/profiles.py +234 -0
  102. package/src/superlocalmemory/core/registry.py +117 -0
  103. package/src/superlocalmemory/dynamics/__init__.py +0 -0
  104. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
  105. package/src/superlocalmemory/encoding/__init__.py +0 -0
  106. package/src/superlocalmemory/encoding/consolidator.py +485 -0
  107. package/src/superlocalmemory/encoding/emotional.py +125 -0
  108. package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
  109. package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
  110. package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
  111. package/src/superlocalmemory/encoding/foresight.py +91 -0
  112. package/src/superlocalmemory/encoding/graph_builder.py +302 -0
  113. package/src/superlocalmemory/encoding/observation_builder.py +160 -0
  114. package/src/superlocalmemory/encoding/scene_builder.py +183 -0
  115. package/src/superlocalmemory/encoding/signal_inference.py +90 -0
  116. package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
  117. package/src/superlocalmemory/encoding/type_router.py +235 -0
  118. package/src/superlocalmemory/hooks/__init__.py +3 -0
  119. package/src/superlocalmemory/hooks/auto_capture.py +111 -0
  120. package/src/superlocalmemory/hooks/auto_recall.py +93 -0
  121. package/src/superlocalmemory/hooks/ide_connector.py +204 -0
  122. package/src/superlocalmemory/hooks/rules_engine.py +99 -0
  123. package/src/superlocalmemory/infra/__init__.py +3 -0
  124. package/src/superlocalmemory/infra/auth_middleware.py +82 -0
  125. package/src/superlocalmemory/infra/backup.py +317 -0
  126. package/src/superlocalmemory/infra/cache_manager.py +267 -0
  127. package/src/superlocalmemory/infra/event_bus.py +381 -0
  128. package/src/superlocalmemory/infra/rate_limiter.py +135 -0
  129. package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
  130. package/src/superlocalmemory/learning/__init__.py +0 -0
  131. package/src/superlocalmemory/learning/adaptive.py +172 -0
  132. package/src/superlocalmemory/learning/behavioral.py +490 -0
  133. package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
  134. package/src/superlocalmemory/learning/bootstrap.py +298 -0
  135. package/src/superlocalmemory/learning/cross_project.py +399 -0
  136. package/src/superlocalmemory/learning/database.py +376 -0
  137. package/src/superlocalmemory/learning/engagement.py +323 -0
  138. package/src/superlocalmemory/learning/features.py +138 -0
  139. package/src/superlocalmemory/learning/feedback.py +316 -0
  140. package/src/superlocalmemory/learning/outcomes.py +255 -0
  141. package/src/superlocalmemory/learning/project_context.py +366 -0
  142. package/src/superlocalmemory/learning/ranker.py +155 -0
  143. package/src/superlocalmemory/learning/source_quality.py +303 -0
  144. package/src/superlocalmemory/learning/workflows.py +309 -0
  145. package/src/superlocalmemory/llm/__init__.py +0 -0
  146. package/src/superlocalmemory/llm/backbone.py +316 -0
  147. package/src/superlocalmemory/math/__init__.py +0 -0
  148. package/src/superlocalmemory/math/fisher.py +356 -0
  149. package/src/superlocalmemory/math/langevin.py +398 -0
  150. package/src/superlocalmemory/math/sheaf.py +257 -0
  151. package/src/superlocalmemory/mcp/__init__.py +0 -0
  152. package/src/superlocalmemory/mcp/resources.py +245 -0
  153. package/src/superlocalmemory/mcp/server.py +61 -0
  154. package/src/superlocalmemory/mcp/tools.py +18 -0
  155. package/src/superlocalmemory/mcp/tools_core.py +305 -0
  156. package/src/superlocalmemory/mcp/tools_v28.py +223 -0
  157. package/src/superlocalmemory/mcp/tools_v3.py +286 -0
  158. package/src/superlocalmemory/retrieval/__init__.py +0 -0
  159. package/src/superlocalmemory/retrieval/agentic.py +295 -0
  160. package/src/superlocalmemory/retrieval/ann_index.py +223 -0
  161. package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
  162. package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
  163. package/src/superlocalmemory/retrieval/engine.py +390 -0
  164. package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
  165. package/src/superlocalmemory/retrieval/fusion.py +78 -0
  166. package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
  167. package/src/superlocalmemory/retrieval/reranker.py +154 -0
  168. package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
  169. package/src/superlocalmemory/retrieval/strategy.py +96 -0
  170. package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
  171. package/src/superlocalmemory/server/__init__.py +1 -0
  172. package/src/superlocalmemory/server/api.py +248 -0
  173. package/src/superlocalmemory/server/routes/__init__.py +4 -0
  174. package/src/superlocalmemory/server/routes/agents.py +107 -0
  175. package/src/superlocalmemory/server/routes/backup.py +91 -0
  176. package/src/superlocalmemory/server/routes/behavioral.py +127 -0
  177. package/src/superlocalmemory/server/routes/compliance.py +160 -0
  178. package/src/superlocalmemory/server/routes/data_io.py +188 -0
  179. package/src/superlocalmemory/server/routes/events.py +183 -0
  180. package/src/superlocalmemory/server/routes/helpers.py +85 -0
  181. package/src/superlocalmemory/server/routes/learning.py +273 -0
  182. package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
  183. package/src/superlocalmemory/server/routes/memories.py +399 -0
  184. package/src/superlocalmemory/server/routes/profiles.py +219 -0
  185. package/src/superlocalmemory/server/routes/stats.py +346 -0
  186. package/src/superlocalmemory/server/routes/v3_api.py +365 -0
  187. package/src/superlocalmemory/server/routes/ws.py +82 -0
  188. package/src/superlocalmemory/server/security_middleware.py +57 -0
  189. package/src/superlocalmemory/server/ui.py +245 -0
  190. package/src/superlocalmemory/storage/__init__.py +0 -0
  191. package/src/superlocalmemory/storage/access_control.py +182 -0
  192. package/src/superlocalmemory/storage/database.py +594 -0
  193. package/src/superlocalmemory/storage/migrations.py +303 -0
  194. package/src/superlocalmemory/storage/models.py +406 -0
  195. package/src/superlocalmemory/storage/schema.py +726 -0
  196. package/src/superlocalmemory/storage/v2_migrator.py +317 -0
  197. package/src/superlocalmemory/trust/__init__.py +0 -0
  198. package/src/superlocalmemory/trust/gate.py +130 -0
  199. package/src/superlocalmemory/trust/provenance.py +124 -0
  200. package/src/superlocalmemory/trust/scorer.py +347 -0
  201. package/src/superlocalmemory/trust/signals.py +153 -0
  202. package/ui/index.html +278 -5
  203. package/ui/js/auto-settings.js +70 -0
  204. package/ui/js/dashboard.js +90 -0
  205. package/ui/js/fact-detail.js +92 -0
  206. package/ui/js/feedback.js +2 -2
  207. package/ui/js/ide-status.js +102 -0
  208. package/ui/js/math-health.js +98 -0
  209. package/ui/js/recall-lab.js +127 -0
  210. package/ui/js/settings.js +2 -2
  211. package/ui/js/trust-dashboard.js +73 -0
  212. package/api_server.py +0 -724
  213. package/bin/aider-smart +0 -72
  214. package/bin/superlocalmemoryv2-learning +0 -4
  215. package/bin/superlocalmemoryv2-list +0 -3
  216. package/bin/superlocalmemoryv2-patterns +0 -4
  217. package/bin/superlocalmemoryv2-profile +0 -3
  218. package/bin/superlocalmemoryv2-recall +0 -3
  219. package/bin/superlocalmemoryv2-remember +0 -3
  220. package/bin/superlocalmemoryv2-reset +0 -3
  221. package/bin/superlocalmemoryv2-status +0 -3
  222. package/configs/chatgpt-desktop-mcp.json +0 -16
  223. package/configs/cursor-mcp.json +0 -15
  224. package/hooks/memory-list-skill.js +0 -139
  225. package/hooks/memory-profile-skill.js +0 -273
  226. package/hooks/memory-recall-skill.js +0 -114
  227. package/hooks/memory-remember-skill.js +0 -127
  228. package/hooks/memory-reset-skill.js +0 -274
  229. package/mcp_server.py +0 -1808
  230. package/requirements-core.txt +0 -22
  231. package/requirements-learning.txt +0 -12
  232. package/requirements.txt +0 -12
  233. package/src/agent_registry.py +0 -411
  234. package/src/auth_middleware.py +0 -61
  235. package/src/auto_backup.py +0 -459
  236. package/src/behavioral/__init__.py +0 -49
  237. package/src/behavioral/behavioral_listener.py +0 -203
  238. package/src/behavioral/behavioral_patterns.py +0 -275
  239. package/src/behavioral/cross_project_transfer.py +0 -206
  240. package/src/behavioral/outcome_inference.py +0 -194
  241. package/src/behavioral/outcome_tracker.py +0 -193
  242. package/src/behavioral/tests/__init__.py +0 -4
  243. package/src/behavioral/tests/test_behavioral_integration.py +0 -108
  244. package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
  245. package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
  246. package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
  247. package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
  248. package/src/behavioral/tests/test_outcome_inference.py +0 -107
  249. package/src/behavioral/tests/test_outcome_tracker.py +0 -96
  250. package/src/cache_manager.py +0 -518
  251. package/src/compliance/__init__.py +0 -48
  252. package/src/compliance/abac_engine.py +0 -149
  253. package/src/compliance/abac_middleware.py +0 -116
  254. package/src/compliance/audit_db.py +0 -215
  255. package/src/compliance/audit_logger.py +0 -148
  256. package/src/compliance/retention_manager.py +0 -289
  257. package/src/compliance/retention_scheduler.py +0 -186
  258. package/src/compliance/tests/__init__.py +0 -4
  259. package/src/compliance/tests/test_abac_enforcement.py +0 -95
  260. package/src/compliance/tests/test_abac_engine.py +0 -124
  261. package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
  262. package/src/compliance/tests/test_audit_db.py +0 -123
  263. package/src/compliance/tests/test_audit_logger.py +0 -98
  264. package/src/compliance/tests/test_mcp_audit.py +0 -128
  265. package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
  266. package/src/compliance/tests/test_retention_manager.py +0 -131
  267. package/src/compliance/tests/test_retention_scheduler.py +0 -99
  268. package/src/compression/__init__.py +0 -25
  269. package/src/compression/cli.py +0 -150
  270. package/src/compression/cold_storage.py +0 -217
  271. package/src/compression/config.py +0 -72
  272. package/src/compression/orchestrator.py +0 -133
  273. package/src/compression/tier2_compressor.py +0 -228
  274. package/src/compression/tier3_compressor.py +0 -153
  275. package/src/compression/tier_classifier.py +0 -148
  276. package/src/db_connection_manager.py +0 -536
  277. package/src/embedding_engine.py +0 -63
  278. package/src/embeddings/__init__.py +0 -47
  279. package/src/embeddings/cache.py +0 -70
  280. package/src/embeddings/cli.py +0 -113
  281. package/src/embeddings/constants.py +0 -47
  282. package/src/embeddings/database.py +0 -91
  283. package/src/embeddings/engine.py +0 -247
  284. package/src/embeddings/model_loader.py +0 -145
  285. package/src/event_bus.py +0 -562
  286. package/src/graph/__init__.py +0 -36
  287. package/src/graph/build_helpers.py +0 -74
  288. package/src/graph/cli.py +0 -87
  289. package/src/graph/cluster_builder.py +0 -188
  290. package/src/graph/cluster_summary.py +0 -148
  291. package/src/graph/constants.py +0 -47
  292. package/src/graph/edge_builder.py +0 -162
  293. package/src/graph/entity_extractor.py +0 -95
  294. package/src/graph/graph_core.py +0 -226
  295. package/src/graph/graph_search.py +0 -231
  296. package/src/graph/hierarchical.py +0 -207
  297. package/src/graph/schema.py +0 -99
  298. package/src/graph_engine.py +0 -52
  299. package/src/hnsw_index.py +0 -628
  300. package/src/hybrid_search.py +0 -46
  301. package/src/learning/__init__.py +0 -217
  302. package/src/learning/adaptive_ranker.py +0 -682
  303. package/src/learning/bootstrap/__init__.py +0 -69
  304. package/src/learning/bootstrap/constants.py +0 -93
  305. package/src/learning/bootstrap/db_queries.py +0 -316
  306. package/src/learning/bootstrap/sampling.py +0 -82
  307. package/src/learning/bootstrap/text_utils.py +0 -71
  308. package/src/learning/cross_project_aggregator.py +0 -857
  309. package/src/learning/db/__init__.py +0 -40
  310. package/src/learning/db/constants.py +0 -44
  311. package/src/learning/db/schema.py +0 -279
  312. package/src/learning/engagement_tracker.py +0 -628
  313. package/src/learning/feature_extractor.py +0 -708
  314. package/src/learning/feedback_collector.py +0 -806
  315. package/src/learning/learning_db.py +0 -915
  316. package/src/learning/project_context_manager.py +0 -572
  317. package/src/learning/ranking/__init__.py +0 -33
  318. package/src/learning/ranking/constants.py +0 -84
  319. package/src/learning/ranking/helpers.py +0 -278
  320. package/src/learning/source_quality_scorer.py +0 -676
  321. package/src/learning/synthetic_bootstrap.py +0 -755
  322. package/src/learning/tests/test_adaptive_ranker.py +0 -325
  323. package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
  324. package/src/learning/tests/test_aggregator.py +0 -306
  325. package/src/learning/tests/test_auto_retrain_v28.py +0 -35
  326. package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
  327. package/src/learning/tests/test_feature_extractor_v28.py +0 -93
  328. package/src/learning/tests/test_feedback_collector.py +0 -294
  329. package/src/learning/tests/test_learning_db.py +0 -602
  330. package/src/learning/tests/test_learning_db_v28.py +0 -110
  331. package/src/learning/tests/test_learning_init_v28.py +0 -48
  332. package/src/learning/tests/test_outcome_signals.py +0 -48
  333. package/src/learning/tests/test_project_context.py +0 -292
  334. package/src/learning/tests/test_schema_migration.py +0 -319
  335. package/src/learning/tests/test_signal_inference.py +0 -397
  336. package/src/learning/tests/test_source_quality.py +0 -351
  337. package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
  338. package/src/learning/tests/test_workflow_miner.py +0 -318
  339. package/src/learning/workflow_pattern_miner.py +0 -655
  340. package/src/lifecycle/__init__.py +0 -54
  341. package/src/lifecycle/bounded_growth.py +0 -239
  342. package/src/lifecycle/compaction_engine.py +0 -226
  343. package/src/lifecycle/lifecycle_engine.py +0 -355
  344. package/src/lifecycle/lifecycle_evaluator.py +0 -257
  345. package/src/lifecycle/lifecycle_scheduler.py +0 -130
  346. package/src/lifecycle/retention_policy.py +0 -285
  347. package/src/lifecycle/tests/test_bounded_growth.py +0 -193
  348. package/src/lifecycle/tests/test_compaction.py +0 -179
  349. package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
  350. package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
  351. package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
  352. package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
  353. package/src/lifecycle/tests/test_mcp_compact.py +0 -149
  354. package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
  355. package/src/lifecycle/tests/test_retention_policy.py +0 -162
  356. package/src/mcp_tools_v28.py +0 -281
  357. package/src/memory/__init__.py +0 -36
  358. package/src/memory/cli.py +0 -205
  359. package/src/memory/constants.py +0 -39
  360. package/src/memory/helpers.py +0 -28
  361. package/src/memory/schema.py +0 -166
  362. package/src/memory-profiles.py +0 -595
  363. package/src/memory-reset.py +0 -491
  364. package/src/memory_compression.py +0 -989
  365. package/src/memory_store_v2.py +0 -1155
  366. package/src/migrate_v1_to_v2.py +0 -629
  367. package/src/pattern_learner.py +0 -34
  368. package/src/patterns/__init__.py +0 -24
  369. package/src/patterns/analyzers.py +0 -251
  370. package/src/patterns/learner.py +0 -271
  371. package/src/patterns/scoring.py +0 -171
  372. package/src/patterns/store.py +0 -225
  373. package/src/patterns/terminology.py +0 -140
  374. package/src/provenance_tracker.py +0 -312
  375. package/src/qualixar_attribution.py +0 -139
  376. package/src/qualixar_watermark.py +0 -78
  377. package/src/query_optimizer.py +0 -511
  378. package/src/rate_limiter.py +0 -83
  379. package/src/search/__init__.py +0 -20
  380. package/src/search/cli.py +0 -77
  381. package/src/search/constants.py +0 -26
  382. package/src/search/engine.py +0 -241
  383. package/src/search/fusion.py +0 -122
  384. package/src/search/index_loader.py +0 -114
  385. package/src/search/methods.py +0 -162
  386. package/src/search_engine_v2.py +0 -401
  387. package/src/setup_validator.py +0 -482
  388. package/src/subscription_manager.py +0 -391
  389. package/src/tree/__init__.py +0 -59
  390. package/src/tree/builder.py +0 -185
  391. package/src/tree/nodes.py +0 -202
  392. package/src/tree/queries.py +0 -257
  393. package/src/tree/schema.py +0 -80
  394. package/src/tree_manager.py +0 -19
  395. package/src/trust/__init__.py +0 -45
  396. package/src/trust/constants.py +0 -66
  397. package/src/trust/queries.py +0 -157
  398. package/src/trust/schema.py +0 -95
  399. package/src/trust/scorer.py +0 -299
  400. package/src/trust/signals.py +0 -95
  401. package/src/trust_scorer.py +0 -44
  402. package/ui/app.js +0 -1588
  403. package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
  404. package/ui/js/graph-cytoscape.js +0 -1168
  405. package/ui/js/graph-d3-backup.js +0 -32
  406. package/ui/js/graph.js +0 -32
  407. package/ui_server.py +0 -286
  408. /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
  409. /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
  410. /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
  411. /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
  412. /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
  413. /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
  414. /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
  415. /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
  416. /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
  417. /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
  418. /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
  419. /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
  420. /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
  421. /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
  422. /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
  423. /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
  424. /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
  425. /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
  426. /package/{completions → ide/completions}/slm.bash +0 -0
  427. /package/{completions → ide/completions}/slm.zsh +0 -0
  428. /package/{configs → ide/configs}/cody-commands.json +0 -0
  429. /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
  430. /package/{install.ps1 → scripts/install.ps1} +0 -0
  431. /package/{install.sh → scripts/install.sh} +0 -0
@@ -0,0 +1,179 @@
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 — Entity Graph Channel with Spreading Activation.
6
+
7
+ SA-RAG pattern: entities from query -> canonical lookup -> graph traversal
8
+ with decay. Handles BOTH uppercase and lowercase entity mentions.
9
+
10
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
11
+ License: MIT
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import logging
17
+ import re
18
+ from collections import defaultdict
19
+ from typing import TYPE_CHECKING
20
+
21
+ if TYPE_CHECKING:
22
+ from superlocalmemory.encoding.entity_resolver import EntityResolver
23
+ from superlocalmemory.storage.database import DatabaseManager
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ _PROPER_NOUN_RE = re.compile(r"\b[A-Z][a-z]{1,}\b")
28
+
29
+ _ENTITY_STOP: frozenset[str] = frozenset({
30
+ # Expanded stop list for query entity extraction
31
+ "what", "when", "where", "who", "which", "how", "does", "did",
32
+ "the", "that", "this", "there", "then", "than", "they", "them",
33
+ "have", "has", "had", "been", "being", "about", "after", "before",
34
+ "from", "into", "with", "some", "other", "would", "could", "should",
35
+ "will", "because", "also", "just", "like", "know", "think",
36
+ "feel", "want", "need", "make", "take", "give", "tell", "said",
37
+ "wow", "gonna", "got", "by", "thanks", "thank", "hey", "hi",
38
+ "hello", "bye", "good", "great", "nice", "cool", "right",
39
+ "let", "can", "might", "much", "many", "more", "most",
40
+ "something", "anything", "everything", "nothing", "someone",
41
+ "it", "my", "your", "our", "their", "me", "you", "we", "us",
42
+ "do", "if", "or", "no", "to", "at", "on", "in", "so",
43
+ "go", "come", "see", "look", "say", "ask", "try", "keep",
44
+ "yes", "yeah", "sure", "okay", "ok", "really", "actually",
45
+ "maybe", "well", "still", "even", "very",
46
+ })
47
+
48
+
49
+ def extract_query_entities(query: str) -> list[str]:
50
+ """Extract entity candidates from query (handles both cases).
51
+
52
+ Strategy: find proper nouns in original + title-cased text,
53
+ plus quoted phrases. Deduplicates case-insensitively.
54
+ """
55
+ candidates: list[str] = []
56
+ seen: set[str] = set()
57
+
58
+ def _add(name: str) -> None:
59
+ lo = name.lower()
60
+ if lo not in seen and lo not in _ENTITY_STOP and len(name) >= 2:
61
+ seen.add(lo)
62
+ candidates.append(name)
63
+
64
+ for m in _PROPER_NOUN_RE.finditer(query):
65
+ _add(m.group(0))
66
+ for m in _PROPER_NOUN_RE.finditer(query.title()):
67
+ _add(m.group(0))
68
+ for m in re.finditer(r'"([^"]+)"', query):
69
+ _add(m.group(1).strip())
70
+
71
+ return candidates
72
+
73
+
74
+ class EntityGraphChannel:
75
+ """Entity-based retrieval with spreading activation (SA-RAG)."""
76
+
77
+ def __init__(
78
+ self, db: DatabaseManager,
79
+ entity_resolver: EntityResolver | None = None,
80
+ decay: float = 0.7, activation_threshold: float = 0.1,
81
+ max_hops: int = 3,
82
+ ) -> None:
83
+ self._db = db
84
+ self._resolver = entity_resolver
85
+ self._decay = decay
86
+ self._threshold = activation_threshold
87
+ self._max_hops = max_hops
88
+
89
+ def search(self, query: str, profile_id: str, top_k: int = 50) -> list[tuple[str, float]]:
90
+ """Search via entity graph with spreading activation."""
91
+ raw_entities = extract_query_entities(query)
92
+ if not raw_entities:
93
+ return []
94
+
95
+ canonical_ids = self._resolve_entities(raw_entities, profile_id)
96
+ if not canonical_ids:
97
+ return []
98
+
99
+ # Seed activation from direct entity-linked facts
100
+ activation: dict[str, float] = defaultdict(float)
101
+ visited_entities: set[str] = set(canonical_ids)
102
+
103
+ for eid in canonical_ids:
104
+ for fact in self._db.get_facts_by_entity(eid, profile_id):
105
+ activation[fact.fact_id] = max(activation[fact.fact_id], 1.0)
106
+
107
+ # Spreading activation through graph edges
108
+ frontier = set(activation.keys())
109
+ for hop in range(1, self._max_hops):
110
+ hop_decay = self._decay ** hop
111
+ if hop_decay < self._threshold:
112
+ break
113
+ next_frontier: set[str] = set()
114
+
115
+ for fid in frontier:
116
+ for edge in self._db.get_edges_for_node(fid, profile_id):
117
+ neighbor = edge.target_id if edge.source_id == fid else edge.source_id
118
+ propagated = activation[fid] * self._decay
119
+ if propagated >= self._threshold and propagated > activation.get(neighbor, 0.0):
120
+ activation[neighbor] = propagated
121
+ next_frontier.add(neighbor)
122
+
123
+ # Discover new entities from activated facts -> get their facts
124
+ new_eids = self._discover_entities(frontier, profile_id, visited_entities)
125
+ for eid in new_eids:
126
+ visited_entities.add(eid)
127
+ for fact in self._db.get_facts_by_entity(eid, profile_id):
128
+ if hop_decay > activation.get(fact.fact_id, 0.0):
129
+ activation[fact.fact_id] = hop_decay
130
+ next_frontier.add(fact.fact_id)
131
+
132
+ frontier = next_frontier
133
+ if not frontier:
134
+ break
135
+
136
+ results = [(fid, sc) for fid, sc in activation.items() if sc >= self._threshold]
137
+ results.sort(key=lambda x: x[1], reverse=True)
138
+ return results[:top_k]
139
+
140
+ def _resolve_entities(self, raw: list[str], profile_id: str) -> list[str]:
141
+ """Resolve raw names to canonical entity IDs."""
142
+ ids: list[str] = []
143
+ seen: set[str] = set()
144
+ if self._resolver is not None:
145
+ for eid in self._resolver.resolve(raw, profile_id).values():
146
+ if eid not in seen:
147
+ seen.add(eid)
148
+ ids.append(eid)
149
+ else:
150
+ for name in raw:
151
+ ent = self._db.get_entity_by_name(name, profile_id)
152
+ if ent and ent.entity_id not in seen:
153
+ seen.add(ent.entity_id)
154
+ ids.append(ent.entity_id)
155
+ return ids
156
+
157
+ def _discover_entities(
158
+ self, fact_ids: set[str], profile_id: str, visited: set[str],
159
+ ) -> list[str]:
160
+ """Find new canonical entity IDs referenced by a set of facts."""
161
+ new: list[str] = []
162
+ seen = set(visited)
163
+ for fid in fact_ids:
164
+ rows = self._db.execute(
165
+ "SELECT canonical_entities_json FROM atomic_facts WHERE fact_id = ?", (fid,),
166
+ )
167
+ if not rows:
168
+ continue
169
+ raw = dict(rows[0]).get("canonical_entities_json")
170
+ if not raw:
171
+ continue
172
+ try:
173
+ for eid in json.loads(raw):
174
+ if eid not in seen:
175
+ seen.add(eid)
176
+ new.append(eid)
177
+ except (ValueError, TypeError):
178
+ continue
179
+ return new
@@ -0,0 +1,78 @@
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 — Weighted Reciprocal Rank Fusion.
6
+
7
+ Single-pass RRF with k=60 for diverse retrieval (D116).
8
+ V1 had triple re-fusion which destroyed rankings — fixed in V2.
9
+
10
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
11
+ License: MIT
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from dataclasses import dataclass, field
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class FusionResult:
20
+ """Single fused result with per-channel provenance."""
21
+ fact_id: str
22
+ fused_score: float
23
+ channel_ranks: dict[str, int] = field(default_factory=dict)
24
+ channel_scores: dict[str, float] = field(default_factory=dict)
25
+
26
+
27
+ def weighted_rrf(
28
+ channels: dict[str, list[tuple[str, float]]],
29
+ weights: dict[str, float],
30
+ k: int = 60,
31
+ max_rank_penalty: int = 1000,
32
+ ) -> list[FusionResult]:
33
+ """Fuse ranked lists via Weighted Reciprocal Rank Fusion.
34
+
35
+ Args:
36
+ channels: channel_name -> [(fact_id, score)] sorted desc.
37
+ weights: channel_name -> weight multiplier.
38
+ k: RRF smoothing constant (60 for diverse retrieval, D116).
39
+ max_rank_penalty: Rank assigned to absent documents.
40
+
41
+ Returns:
42
+ FusionResult list sorted by fused_score descending.
43
+ """
44
+ if k <= 0:
45
+ raise ValueError(f"k must be positive, got {k}")
46
+
47
+ rank_maps: dict[str, dict[str, int]] = {}
48
+ score_maps: dict[str, dict[str, float]] = {}
49
+
50
+ for ch, ranked in channels.items():
51
+ ranks: dict[str, int] = {}
52
+ scores: dict[str, float] = {}
53
+ for i, (fid, sc) in enumerate(ranked):
54
+ ranks[fid] = i + 1
55
+ scores[fid] = sc
56
+ rank_maps[ch] = ranks
57
+ score_maps[ch] = scores
58
+
59
+ all_ids: set[str] = set()
60
+ for ranked in channels.values():
61
+ for fid, _ in ranked:
62
+ all_ids.add(fid)
63
+
64
+ results: list[FusionResult] = []
65
+ for fid in all_ids:
66
+ fused = 0.0
67
+ ch_ranks: dict[str, int] = {}
68
+ ch_scores: dict[str, float] = {}
69
+ for ch in channels:
70
+ w = weights.get(ch, 1.0)
71
+ rank = rank_maps[ch].get(fid, max_rank_penalty)
72
+ ch_ranks[ch] = rank
73
+ ch_scores[ch] = score_maps[ch].get(fid, 0.0)
74
+ fused += w / (k + rank)
75
+ results.append(FusionResult(fid, fused, ch_ranks, ch_scores))
76
+
77
+ results.sort(key=lambda r: r.fused_score, reverse=True)
78
+ return results
@@ -0,0 +1,105 @@
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 — Profile Channel (Entity-Profile Retrieval).
6
+
7
+ Returns fact IDs from entity profiles — enables direct answers for
8
+ "What does Alice do?" style queries without full embedding search.
9
+
10
+ This is a SHORTCUT channel: if the query mentions a known entity,
11
+ the profile's accumulated fact IDs are injected directly into the
12
+ retrieval pool with high scores.
13
+
14
+ Competitor reference: EverMemOS profile synthesis (~+15-20% SH).
15
+
16
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
17
+ License: MIT
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import logging
22
+ import re
23
+ from typing import TYPE_CHECKING
24
+
25
+ if TYPE_CHECKING:
26
+ from superlocalmemory.storage.database import DatabaseManager
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ # Pattern for extracting potential entity names (capitalized multi-word)
31
+ _ENTITY_PATTERN = re.compile(r"\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b")
32
+
33
+ # Common sentence starters to exclude from entity extraction
34
+ _SENTENCE_STARTERS = frozenset({
35
+ "What", "Where", "Who", "Which", "How", "When", "Does", "Did",
36
+ "Can", "Could", "Would", "Should", "Are", "Is", "Was", "Were",
37
+ "Has", "Have", "The", "Tell", "Please", "Do", "Why",
38
+ })
39
+
40
+
41
+ class ProfileChannel:
42
+ """Entity-profile-based retrieval for direct entity queries.
43
+
44
+ If the query mentions a known entity (by canonical name or alias),
45
+ the entity's profile fact IDs are returned with high base score.
46
+
47
+ Usage::
48
+
49
+ channel = ProfileChannel(db)
50
+ results = channel.search("What is Alice's job?", "default")
51
+ # returns [(fact_id_1, 0.95), (fact_id_2, 0.95), ...]
52
+ """
53
+
54
+ def __init__(self, db: DatabaseManager) -> None:
55
+ self._db = db
56
+
57
+ def search(
58
+ self,
59
+ query: str,
60
+ profile_id: str,
61
+ top_k: int = 10,
62
+ ) -> list[tuple[str, float]]:
63
+ """Search entity profiles for matching facts.
64
+
65
+ Args:
66
+ query: User query text.
67
+ profile_id: Scope to this profile.
68
+ top_k: Maximum results to return.
69
+
70
+ Returns:
71
+ List of (fact_id, score) sorted by score descending.
72
+ """
73
+ entities = self._extract_entity_names(query)
74
+ if not entities:
75
+ return []
76
+
77
+ results: list[tuple[str, float]] = []
78
+ seen: set[str] = set()
79
+
80
+ for name in entities:
81
+ entity = self._db.get_entity_by_name(name, profile_id)
82
+ if not entity:
83
+ continue
84
+
85
+ profiles = self._db.get_entity_profiles_by_entity(
86
+ entity.entity_id, profile_id,
87
+ )
88
+ for p in profiles:
89
+ for fid in p.fact_ids:
90
+ if fid not in seen:
91
+ seen.add(fid)
92
+ results.append((fid, 0.95))
93
+
94
+ results.sort(key=lambda x: x[1], reverse=True)
95
+ return results[:top_k]
96
+
97
+ @staticmethod
98
+ def _extract_entity_names(query: str) -> list[str]:
99
+ """Extract potential entity names from query text.
100
+
101
+ Returns capitalized words/phrases that aren't common
102
+ sentence starters.
103
+ """
104
+ matches = _ENTITY_PATTERN.findall(query)
105
+ return [m for m in matches if m not in _SENTENCE_STARTERS]
@@ -0,0 +1,154 @@
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 — Cross-Encoder Reranker.
6
+
7
+ Scores (query, fact) pairs through a cross-encoder in a single forward
8
+ pass. Lazy model loading, thread-safe via lock.
9
+
10
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
11
+ License: MIT
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import logging
17
+ import threading
18
+ from typing import Any
19
+
20
+ from superlocalmemory.storage.models import AtomicFact
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class CrossEncoderReranker:
26
+ """Rerank candidate facts using a local cross-encoder model.
27
+
28
+ When the model is unavailable (missing package, download failure,
29
+ offline environment), falls back to returning candidates in their
30
+ original score order — never crashes.
31
+
32
+ Args:
33
+ model_name: HuggingFace cross-encoder model identifier.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ model_name: str = "BAAI/bge-reranker-v2-m3",
39
+ ) -> None:
40
+ self._model_name = model_name
41
+ self._model: Any = None
42
+ self._loaded = False
43
+ self._lock = threading.Lock()
44
+
45
+ # ------------------------------------------------------------------
46
+ # Lazy loading
47
+ # ------------------------------------------------------------------
48
+
49
+ def _ensure_model(self) -> None:
50
+ """Load cross-encoder on first use (thread-safe)."""
51
+ if self._loaded:
52
+ return
53
+
54
+ with self._lock:
55
+ if self._loaded:
56
+ return # Double-check after acquiring lock
57
+ try:
58
+ from sentence_transformers import CrossEncoder
59
+
60
+ self._model = CrossEncoder(self._model_name)
61
+ logger.info("Cross-encoder loaded: %s", self._model_name)
62
+ except ImportError:
63
+ logger.warning(
64
+ "sentence-transformers not installed; "
65
+ "cross-encoder reranking disabled"
66
+ )
67
+ except OSError as exc:
68
+ logger.warning(
69
+ "Failed to load cross-encoder %s: %s",
70
+ self._model_name,
71
+ exc,
72
+ )
73
+ finally:
74
+ self._loaded = True
75
+
76
+ # ------------------------------------------------------------------
77
+ # Public API
78
+ # ------------------------------------------------------------------
79
+
80
+ def rerank(
81
+ self,
82
+ query: str,
83
+ candidates: list[tuple[AtomicFact, float]],
84
+ top_k: int = 10,
85
+ ) -> list[tuple[AtomicFact, float]]:
86
+ """Rerank candidates by cross-encoder relevance.
87
+
88
+ Each (query, fact.content) pair is scored in a single forward
89
+ pass. Results are returned sorted by cross-encoder score.
90
+
91
+ When the model is unavailable, returns candidates sorted by
92
+ their existing score (graceful fallback).
93
+
94
+ Args:
95
+ query: User query text.
96
+ candidates: List of (AtomicFact, score) tuples from the
97
+ fusion stage.
98
+ top_k: Maximum results to return.
99
+
100
+ Returns:
101
+ Top-k (AtomicFact, cross_encoder_score) tuples, sorted
102
+ descending by cross-encoder score.
103
+ """
104
+ if not candidates:
105
+ return []
106
+
107
+ self._ensure_model()
108
+
109
+ if self._model is None:
110
+ # Fallback: keep existing score order
111
+ sorted_cands = sorted(
112
+ candidates, key=lambda x: x[1], reverse=True
113
+ )
114
+ return sorted_cands[:top_k]
115
+
116
+ # Build (query, document) pairs for batch scoring
117
+ pairs: list[tuple[str, str]] = [
118
+ (query, fact.content) for fact, _ in candidates
119
+ ]
120
+
121
+ scores = self._model.predict(pairs)
122
+
123
+ scored: list[tuple[AtomicFact, float]] = [
124
+ (fact, float(score))
125
+ for (fact, _), score in zip(candidates, scores)
126
+ ]
127
+
128
+ scored.sort(key=lambda x: x[1], reverse=True)
129
+ return scored[:top_k]
130
+
131
+ def score_pair(self, query: str, document: str) -> float:
132
+ """Score a single (query, document) pair.
133
+
134
+ Args:
135
+ query: Query text.
136
+ document: Document text.
137
+
138
+ Returns:
139
+ Relevance score (higher = more relevant). 0.0 if model
140
+ is unavailable.
141
+ """
142
+ self._ensure_model()
143
+
144
+ if self._model is None:
145
+ return 0.0
146
+
147
+ scores = self._model.predict([(query, document)])
148
+ return float(scores[0])
149
+
150
+ @property
151
+ def is_available(self) -> bool:
152
+ """Whether the cross-encoder model is loaded and ready."""
153
+ self._ensure_model()
154
+ return self._model is not None