superlocalmemory 3.6.22 → 3.7.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 (303) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +275 -72
  3. package/bin/slm-npm +43 -89
  4. package/docs/pi-dev-integration.md +43 -0
  5. package/ide/configs/antigravity-mcp.json +2 -2
  6. package/ide/configs/chatgpt-desktop-mcp.json +1 -1
  7. package/ide/configs/claude-desktop-mcp.json +2 -2
  8. package/ide/configs/windsurf-mcp.json +2 -2
  9. package/ide/hooks/context-hook.js +6 -2
  10. package/ide/hooks/post-recall-hook.js +7 -3
  11. package/ide/hooks/tool-event-hook.sh +2 -1
  12. package/package.json +19 -10
  13. package/plugin/.claude-plugin/plugin.json +1 -1
  14. package/plugin/_GENERATED.md +1 -1
  15. package/plugin/agents/slm-memory-advisor.md +1 -1
  16. package/plugin/requirements.txt +1 -1
  17. package/plugin/skills/slm-session/SKILL.md +1 -1
  18. package/plugin-src/rules/AGENTS.md +1 -1
  19. package/pyproject.toml +40 -8
  20. package/scripts/postinstall-interactive.js +17 -94
  21. package/scripts/postinstall.js +185 -258
  22. package/scripts/preuninstall.js +9 -50
  23. package/src/superlocalmemory/__init__.py +2 -2
  24. package/src/superlocalmemory/attribution/mathematical_dna.py +1 -1
  25. package/src/superlocalmemory/attribution/signer.py +34 -19
  26. package/src/superlocalmemory/attribution/watermark.py +1 -1
  27. package/src/superlocalmemory/cli/_lazy_init.py +3 -5
  28. package/src/superlocalmemory/cli/commands.py +490 -195
  29. package/src/superlocalmemory/cli/context_commands.py +5 -4
  30. package/src/superlocalmemory/cli/daemon.py +282 -187
  31. package/src/superlocalmemory/cli/db_migrate.py +3 -1
  32. package/src/superlocalmemory/cli/diagnostics_cmd.py +28 -0
  33. package/src/superlocalmemory/cli/evidence_cmd.py +103 -0
  34. package/src/superlocalmemory/cli/ingest_cmd.py +7 -3
  35. package/src/superlocalmemory/cli/main.py +128 -31
  36. package/src/superlocalmemory/cli/pending_store.py +54 -38
  37. package/src/superlocalmemory/cli/scale_engine_cmd.py +37 -0
  38. package/src/superlocalmemory/cli/service_installer.py +57 -52
  39. package/src/superlocalmemory/cli/setup_wizard.py +142 -88
  40. package/src/superlocalmemory/cli/version_banner.py +2 -1
  41. package/src/superlocalmemory/code_graph/config.py +3 -1
  42. package/src/superlocalmemory/core/backend_orchestrator.py +81 -21
  43. package/src/superlocalmemory/core/config.py +65 -20
  44. package/src/superlocalmemory/core/consolidation_engine.py +9 -7
  45. package/src/superlocalmemory/core/context_cache.py +56 -8
  46. package/src/superlocalmemory/core/derivation_lineage.py +246 -0
  47. package/src/superlocalmemory/core/embedding_worker.py +32 -20
  48. package/src/superlocalmemory/core/embeddings.py +54 -18
  49. package/src/superlocalmemory/core/engine.py +150 -104
  50. package/src/superlocalmemory/core/engine_ingestion.py +513 -0
  51. package/src/superlocalmemory/core/engine_wiring.py +2 -0
  52. package/src/superlocalmemory/core/evidence_bundle.py +526 -0
  53. package/src/superlocalmemory/core/fact_consolidator.py +5 -11
  54. package/src/superlocalmemory/core/graph_analyzer.py +2 -2
  55. package/src/superlocalmemory/core/health_monitor.py +4 -2
  56. package/src/superlocalmemory/core/ingestion_command.py +636 -0
  57. package/src/superlocalmemory/core/injection.py +69 -18
  58. package/src/superlocalmemory/core/lifecycle_state.py +153 -0
  59. package/src/superlocalmemory/core/maintenance.py +23 -22
  60. package/src/superlocalmemory/core/maintenance_scheduler.py +51 -35
  61. package/src/superlocalmemory/core/mutations.py +143 -0
  62. package/src/superlocalmemory/core/platform_utils.py +7 -4
  63. package/src/superlocalmemory/core/ram_lock.py +16 -5
  64. package/src/superlocalmemory/core/rate_limit.py +1 -1
  65. package/src/superlocalmemory/core/recall_pipeline.py +60 -101
  66. package/src/superlocalmemory/core/recall_worker.py +76 -59
  67. package/src/superlocalmemory/core/registry.py +1 -1
  68. package/src/superlocalmemory/core/scale_engine.py +293 -0
  69. package/src/superlocalmemory/core/score_contract.py +62 -0
  70. package/src/superlocalmemory/core/security_primitives.py +3 -1
  71. package/src/superlocalmemory/core/slm_disabled.py +3 -5
  72. package/src/superlocalmemory/core/store_pipeline.py +172 -40
  73. package/src/superlocalmemory/core/tier_manager.py +32 -20
  74. package/src/superlocalmemory/core/worker_pool.py +13 -4
  75. package/src/superlocalmemory/dynamics/activation_guided_quantization.py +1 -1
  76. package/src/superlocalmemory/dynamics/eap_scheduler.py +10 -3
  77. package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +1 -1
  78. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +1 -1
  79. package/src/superlocalmemory/encoding/auto_linker.py +1 -1
  80. package/src/superlocalmemory/encoding/cognitive_consolidator.py +7 -16
  81. package/src/superlocalmemory/encoding/consolidator.py +22 -5
  82. package/src/superlocalmemory/encoding/fact_extractor.py +1 -1
  83. package/src/superlocalmemory/encoding/foresight.py +2 -0
  84. package/src/superlocalmemory/encoding/graph_builder.py +1 -1
  85. package/src/superlocalmemory/encoding/temporal_parser.py +2 -0
  86. package/src/superlocalmemory/evaluation/__init__.py +13 -0
  87. package/src/superlocalmemory/evaluation/calibration.py +308 -0
  88. package/src/superlocalmemory/evolution/skill_evolver.py +2 -1
  89. package/src/superlocalmemory/graph/cozo_backend.py +256 -23
  90. package/src/superlocalmemory/hooks/_outcome_common.py +21 -11
  91. package/src/superlocalmemory/hooks/antigravity_adapter.py +10 -31
  92. package/src/superlocalmemory/hooks/auto_invoker.py +25 -27
  93. package/src/superlocalmemory/hooks/auto_recall.py +31 -6
  94. package/src/superlocalmemory/hooks/auto_recall_hook.py +13 -33
  95. package/src/superlocalmemory/hooks/before_web_hook.py +9 -7
  96. package/src/superlocalmemory/hooks/claude_code_hooks.py +126 -39
  97. package/src/superlocalmemory/hooks/codex_assets.py +59 -0
  98. package/src/superlocalmemory/hooks/codex_hooks.py +186 -0
  99. package/src/superlocalmemory/hooks/context_payload.py +1 -1
  100. package/src/superlocalmemory/hooks/copilot_adapter.py +9 -24
  101. package/src/superlocalmemory/hooks/cursor_adapter.py +10 -32
  102. package/src/superlocalmemory/hooks/hook_daemon.py +4 -2
  103. package/src/superlocalmemory/hooks/hook_handlers.py +241 -55
  104. package/src/superlocalmemory/hooks/memory_protocol.py +5 -3
  105. package/src/superlocalmemory/hooks/post_tool_async_hook.py +4 -2
  106. package/src/superlocalmemory/hooks/session_registry.py +15 -8
  107. package/src/superlocalmemory/hooks/stop_outcome_hook.py +10 -6
  108. package/src/superlocalmemory/hooks/topic_shift_hook.py +42 -12
  109. package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -14
  110. package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +19 -11
  111. package/src/superlocalmemory/infra/auth_middleware.py +38 -5
  112. package/src/superlocalmemory/infra/backup.py +7 -5
  113. package/src/superlocalmemory/infra/cloud_backup.py +18 -8
  114. package/src/superlocalmemory/infra/daemon_identity.py +248 -0
  115. package/src/superlocalmemory/infra/data_root.py +199 -0
  116. package/src/superlocalmemory/infra/event_bus.py +3 -1
  117. package/src/superlocalmemory/infra/local_diagnostics.py +327 -0
  118. package/src/superlocalmemory/infra/process_reaper.py +23 -0
  119. package/src/superlocalmemory/ingestion/adapter_manager.py +27 -9
  120. package/src/superlocalmemory/ingestion/base_adapter.py +25 -31
  121. package/src/superlocalmemory/ingestion/calendar_adapter.py +13 -4
  122. package/src/superlocalmemory/ingestion/credentials.py +14 -7
  123. package/src/superlocalmemory/ingestion/gmail_adapter.py +13 -4
  124. package/src/superlocalmemory/ingestion/transcript_adapter.py +7 -2
  125. package/src/superlocalmemory/learning/consolidation_quantization_worker.py +1 -1
  126. package/src/superlocalmemory/learning/ensemble.py +11 -0
  127. package/src/superlocalmemory/learning/entity_compiler.py +1 -1
  128. package/src/superlocalmemory/learning/feedback.py +1 -1
  129. package/src/superlocalmemory/learning/forgetting_scheduler.py +12 -7
  130. package/src/superlocalmemory/learning/quantization_scheduler.py +1 -1
  131. package/src/superlocalmemory/learning/ranker.py +4 -1
  132. package/src/superlocalmemory/learning/source_quality.py +1 -1
  133. package/src/superlocalmemory/learning/trigram_index.py +3 -2
  134. package/src/superlocalmemory/llm/backbone.py +13 -8
  135. package/src/superlocalmemory/math/ebbinghaus.py +1 -1
  136. package/src/superlocalmemory/math/fisher.py +1 -1
  137. package/src/superlocalmemory/math/fisher_quantized.py +1 -1
  138. package/src/superlocalmemory/math/hopfield.py +1 -1
  139. package/src/superlocalmemory/math/langevin.py +1 -1
  140. package/src/superlocalmemory/math/polar_quant.py +3 -4
  141. package/src/superlocalmemory/math/qjl.py +1 -1
  142. package/src/superlocalmemory/math/sheaf.py +1 -1
  143. package/src/superlocalmemory/math/turbo_quant.py +3 -2
  144. package/src/superlocalmemory/mcp/_daemon_proxy.py +12 -11
  145. package/src/superlocalmemory/mcp/_pool_adapter.py +27 -0
  146. package/src/superlocalmemory/mcp/http_transport.py +53 -0
  147. package/src/superlocalmemory/mcp/server.py +39 -13
  148. package/src/superlocalmemory/mcp/shared.py +69 -3
  149. package/src/superlocalmemory/mcp/tools_active.py +141 -31
  150. package/src/superlocalmemory/mcp/tools_core.py +128 -29
  151. package/src/superlocalmemory/mcp/tools_evolution.py +5 -7
  152. package/src/superlocalmemory/mcp/tools_learning.py +42 -2
  153. package/src/superlocalmemory/mcp/tools_mesh.py +7 -23
  154. package/src/superlocalmemory/mcp/tools_optimize.py +8 -1
  155. package/src/superlocalmemory/mcp/tools_v28.py +23 -2
  156. package/src/superlocalmemory/mcp/tools_v3.py +26 -1
  157. package/src/superlocalmemory/mcp/tools_v33.py +56 -17
  158. package/src/superlocalmemory/mesh/broker.py +2 -0
  159. package/src/superlocalmemory/mesh/remote_sync.py +50 -12
  160. package/src/superlocalmemory/optimize/cache/manager.py +77 -1
  161. package/src/superlocalmemory/optimize/cache/semantic.py +23 -3
  162. package/src/superlocalmemory/optimize/compress/ccr.py +4 -0
  163. package/src/superlocalmemory/optimize/compress/router.py +6 -1
  164. package/src/superlocalmemory/optimize/config/__init__.py +5 -0
  165. package/src/superlocalmemory/optimize/config/store.py +6 -4
  166. package/src/superlocalmemory/optimize/proxy/_helpers.py +15 -5
  167. package/src/superlocalmemory/optimize/proxy/capture.py +3 -2
  168. package/src/superlocalmemory/optimize/proxy/server.py +2 -2
  169. package/src/superlocalmemory/optimize/storage/db.py +14 -13
  170. package/src/superlocalmemory/retrieval/agentic.py +1 -1
  171. package/src/superlocalmemory/retrieval/ann_index.py +1 -1
  172. package/src/superlocalmemory/retrieval/bm25_channel.py +35 -11
  173. package/src/superlocalmemory/retrieval/bridge_discovery.py +73 -8
  174. package/src/superlocalmemory/retrieval/engine.py +169 -79
  175. package/src/superlocalmemory/retrieval/entity_channel.py +289 -67
  176. package/src/superlocalmemory/retrieval/forgetting_filter.py +1 -1
  177. package/src/superlocalmemory/retrieval/fusion.py +1 -1
  178. package/src/superlocalmemory/retrieval/hopfield_channel.py +118 -30
  179. package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
  180. package/src/superlocalmemory/retrieval/quantization_aware_search.py +16 -10
  181. package/src/superlocalmemory/retrieval/reranker.py +56 -20
  182. package/src/superlocalmemory/retrieval/scope_policy.py +85 -0
  183. package/src/superlocalmemory/retrieval/semantic_channel.py +122 -14
  184. package/src/superlocalmemory/retrieval/spreading_activation.py +141 -25
  185. package/src/superlocalmemory/retrieval/strategy.py +1 -1
  186. package/src/superlocalmemory/retrieval/temporal_channel.py +30 -15
  187. package/src/superlocalmemory/retrieval/vector_store.py +1 -1
  188. package/src/superlocalmemory/server/api.py +10 -7
  189. package/src/superlocalmemory/server/bandit_loops.py +4 -2
  190. package/src/superlocalmemory/server/recall_serializer.py +24 -0
  191. package/src/superlocalmemory/server/route_mutations.py +84 -0
  192. package/src/superlocalmemory/server/routes/agents.py +8 -6
  193. package/src/superlocalmemory/server/routes/brain.py +14 -12
  194. package/src/superlocalmemory/server/routes/chat.py +29 -12
  195. package/src/superlocalmemory/server/routes/data_io.py +55 -24
  196. package/src/superlocalmemory/server/routes/helpers.py +29 -4
  197. package/src/superlocalmemory/server/routes/ingest.py +53 -36
  198. package/src/superlocalmemory/server/routes/memories.py +104 -43
  199. package/src/superlocalmemory/server/routes/mesh.py +31 -0
  200. package/src/superlocalmemory/server/routes/profiles.py +26 -4
  201. package/src/superlocalmemory/server/routes/tiers.py +43 -11
  202. package/src/superlocalmemory/server/routes/timeline.py +5 -1
  203. package/src/superlocalmemory/server/routes/v3_api.py +76 -21
  204. package/src/superlocalmemory/server/security_middleware.py +1 -1
  205. package/src/superlocalmemory/server/ui.py +6 -3
  206. package/src/superlocalmemory/server/unified_daemon.py +680 -293
  207. package/src/superlocalmemory/server/write_identity.py +147 -0
  208. package/src/superlocalmemory/storage/access_log.py +4 -3
  209. package/src/superlocalmemory/storage/database.py +118 -25
  210. package/src/superlocalmemory/storage/migration_runner.py +84 -1
  211. package/src/superlocalmemory/storage/migration_v33.py +1 -1
  212. package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +6 -60
  213. package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +120 -0
  214. package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +54 -0
  215. package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +52 -0
  216. package/src/superlocalmemory/storage/migrations/__init__.py +5 -0
  217. package/src/superlocalmemory/storage/models.py +16 -0
  218. package/src/superlocalmemory/storage/quantized_store.py +20 -3
  219. package/src/superlocalmemory/storage/v2_migrator.py +5 -3
  220. package/src/superlocalmemory/ui/favicon.svg +5 -0
  221. package/src/superlocalmemory/ui/index.html +1 -0
  222. package/src/superlocalmemory/ui/js/compliance.js +1 -1
  223. package/src/superlocalmemory/ui/js/core.js +49 -8
  224. package/src/superlocalmemory/ui/js/dashboard.js +23 -2
  225. package/src/superlocalmemory/ui/js/feedback.js +1 -1
  226. package/src/superlocalmemory/ui/js/graph-filters.js +1 -1
  227. package/src/superlocalmemory/ui/js/graph-ui.js +1 -1
  228. package/src/superlocalmemory/ui/js/lifecycle.js +1 -1
  229. package/src/superlocalmemory/ui/js/ng-mesh.js +15 -49
  230. package/src/superlocalmemory/ui/js/settings.js +4 -2
  231. package/src/superlocalmemory/vector/lancedb_backend.py +57 -9
  232. package/bin/slm +0 -59
  233. package/bin/slm.bat +0 -77
  234. package/bin/slm.cmd +0 -5
  235. package/ide/integrations/langchain/README.md +0 -106
  236. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +0 -9
  237. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +0 -201
  238. package/ide/integrations/langchain/pyproject.toml +0 -38
  239. package/ide/integrations/langchain/tests/__init__.py +0 -3
  240. package/ide/integrations/langchain/tests/test_chat_message_history.py +0 -215
  241. package/ide/integrations/langchain/tests/test_security.py +0 -117
  242. package/ide/integrations/llamaindex/README.md +0 -81
  243. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +0 -9
  244. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +0 -316
  245. package/ide/integrations/llamaindex/pyproject.toml +0 -43
  246. package/ide/integrations/llamaindex/tests/__init__.py +0 -3
  247. package/ide/integrations/llamaindex/tests/test_chat_store.py +0 -294
  248. package/ide/integrations/llamaindex/tests/test_security.py +0 -241
  249. package/plugin-src/.mcp.json +0 -12
  250. package/plugin-src/agents/slm-memory-advisor.md +0 -44
  251. package/plugin-src/agents/slm-optimize-advisor.md +0 -38
  252. package/plugin-src/hooks/.gitkeep +0 -0
  253. package/plugin-src/hooks/hooks.json +0 -23
  254. package/plugin-src/manifest.json +0 -25
  255. package/plugin-src/requirements.txt +0 -1
  256. package/plugin-src/rules/CLAUDE.md.fragment +0 -44
  257. package/plugin-src/scripts/ensure-venv.bat +0 -122
  258. package/plugin-src/scripts/ensure-venv.sh +0 -105
  259. package/plugin-src/scripts/slm-launch +0 -15
  260. package/plugin-src/scripts/slm-launch.bat +0 -17
  261. package/plugin-src/settings.json +0 -16
  262. package/plugin-src/skills/slm-cache/SKILL.md +0 -140
  263. package/plugin-src/skills/slm-compress/SKILL.md +0 -143
  264. package/plugin-src/skills/slm-graph/SKILL.md +0 -300
  265. package/plugin-src/skills/slm-recall/SKILL.md +0 -204
  266. package/plugin-src/skills/slm-remember/SKILL.md +0 -194
  267. package/plugin-src/skills/slm-session/SKILL.md +0 -207
  268. package/plugin-src/skills/slm-status/SKILL.md +0 -149
  269. package/scripts/__tests__/build-plugin.test.mjs +0 -613
  270. package/scripts/_savings_math.py +0 -270
  271. package/scripts/build-dmg.sh +0 -417
  272. package/scripts/build-plugin.js +0 -742
  273. package/scripts/build-slm-hook.ps1 +0 -40
  274. package/scripts/build-slm-hook.sh +0 -45
  275. package/scripts/build_entry.py +0 -452
  276. package/scripts/ci/stage5b_gate.sh +0 -50
  277. package/scripts/dogfood_savings.py +0 -490
  278. package/scripts/generate-thumbnails.py +0 -218
  279. package/scripts/install-skills.ps1 +0 -4
  280. package/scripts/install-skills.sh +0 -5
  281. package/scripts/install.ps1 +0 -701
  282. package/scripts/install.sh +0 -1015
  283. package/scripts/postinstall_binary.js +0 -287
  284. package/scripts/prepack.js +0 -33
  285. package/scripts/release_manifest.py +0 -273
  286. package/scripts/slm-hook.spec +0 -56
  287. package/scripts/start-dashboard.ps1 +0 -52
  288. package/scripts/start-dashboard.sh +0 -41
  289. package/scripts/sync-wiki.ps1 +0 -127
  290. package/scripts/sync-wiki.sh +0 -82
  291. package/scripts/test-dmg.sh +0 -161
  292. package/scripts/test-npm-package.ps1 +0 -252
  293. package/scripts/test-npm-package.sh +0 -207
  294. package/scripts/verify-install.ps1 +0 -294
  295. package/scripts/verify-install.sh +0 -266
  296. package/scripts/verify-v27.ps1 +0 -301
  297. package/scripts/verify-v27.sh +0 -233
  298. package/src/superlocalmemory.egg-info/PKG-INFO +0 -513
  299. package/src/superlocalmemory.egg-info/SOURCES.txt +0 -529
  300. package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
  301. package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
  302. package/src/superlocalmemory.egg-info/requires.txt +0 -71
  303. package/src/superlocalmemory.egg-info/top_level.txt +0 -1
@@ -38,9 +38,9 @@ import dataclasses
38
38
  import json
39
39
  import logging
40
40
  import os
41
+ import platform
41
42
  import re
42
43
  import sqlite3
43
- import struct
44
44
  import time
45
45
  import uuid
46
46
  import zlib
@@ -49,12 +49,13 @@ from pathlib import Path
49
49
  from typing import Any
50
50
 
51
51
  from cryptography.exceptions import InvalidTag
52
+ from cryptography.hazmat.primitives import hashes
52
53
  from cryptography.hazmat.primitives.ciphers.aead import AESGCM
53
54
  from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
54
- from cryptography.hazmat.primitives import hashes
55
55
 
56
- from superlocalmemory.storage.database import DatabaseManager
56
+ from superlocalmemory.infra.data_root import DynamicStatePath, state_path
57
57
  from superlocalmemory.optimize.storage import schema as _schema
58
+ from superlocalmemory.storage.database import DatabaseManager
58
59
 
59
60
  logger = logging.getLogger(__name__)
60
61
 
@@ -88,7 +89,7 @@ MID_FILENAME: str = ".llmcache_key"
88
89
  SALT_PREFIX: str = "salt:"
89
90
 
90
91
  # C-06: persisted AES key — survives machine-id changes after first run
91
- _KEY_FILE: Path = Path.home() / LLMCACHE_DIRNAME / "opt-key.bin"
92
+ _KEY_FILE = DynamicStatePath("opt-key.bin")
92
93
 
93
94
  _FORBIDDEN_MEMORY_TABLES: frozenset[str] = frozenset({
94
95
  "memories", "atomic_facts", "profiles", "canonical_entities",
@@ -198,7 +199,7 @@ class CacheDB:
198
199
 
199
200
  def __init__(self, db_path: Path | None = None) -> None:
200
201
  if db_path is None:
201
- db_path = Path.home() / LLMCACHE_DIRNAME / LLMCACHE_DBNAME
202
+ db_path = state_path(LLMCACHE_DBNAME)
202
203
  self._db_path = Path(db_path)
203
204
  self._db_path.parent.mkdir(parents=True, exist_ok=True)
204
205
  # If the file exists but is not a valid SQLite database (e.g. user
@@ -285,11 +286,10 @@ class CacheDB:
285
286
 
286
287
  def _get_machine_id(self) -> str:
287
288
  """Return a stable machine identifier for AES key derivation."""
288
- system = os.uname().sysname
289
+ system = platform.system()
289
290
  mid: str | None = None
290
291
  if system == "Darwin":
291
292
  try:
292
- import plistlib
293
293
  import subprocess
294
294
  out = subprocess.run(
295
295
  ["ioreg", "-rd1", "-c", "IOPlatformExpertDevice"],
@@ -310,7 +310,7 @@ class CacheDB:
310
310
  except OSError:
311
311
  mid = None
312
312
  if not mid:
313
- mid_file = Path.home() / LLMCACHE_DIRNAME / MID_FILENAME
313
+ mid_file = state_path(MID_FILENAME)
314
314
  if mid_file.exists():
315
315
  try:
316
316
  mid = mid_file.read_text(encoding="utf-8").strip()
@@ -334,9 +334,10 @@ class CacheDB:
334
334
  so changing the underlying machine-id string cannot invalidate existing
335
335
  cache entries.
336
336
  """
337
+ key_file = Path(_KEY_FILE)
337
338
  try:
338
- if _KEY_FILE.exists():
339
- key = _KEY_FILE.read_bytes()
339
+ if key_file.exists():
340
+ key = key_file.read_bytes()
340
341
  if len(key) == 32:
341
342
  return key
342
343
  except Exception as exc:
@@ -346,9 +347,9 @@ class CacheDB:
346
347
  machine_id = self._get_machine_id()
347
348
  key = self._derive_aes_key(machine_id, salt)
348
349
  try:
349
- _KEY_FILE.parent.mkdir(parents=True, exist_ok=True)
350
- _KEY_FILE.write_bytes(key)
351
- os.chmod(_KEY_FILE, 0o600)
350
+ key_file.parent.mkdir(parents=True, exist_ok=True)
351
+ key_file.write_bytes(key)
352
+ os.chmod(key_file, 0o600)
352
353
  except Exception as exc:
353
354
  logger.warning("CacheDB: could not persist AES key (fail-open): %s", exc)
354
355
  return key
@@ -15,7 +15,7 @@ Design decisions:
15
15
  - Mode C: LLM sufficiency judgment with 3-way classification
16
16
 
17
17
  Part of Qualixar | Author: Varun Pratap Bhardwaj
18
- License: Elastic-2.0
18
+ License: AGPL-3.0-or-later
19
19
  """
20
20
  from __future__ import annotations
21
21
 
@@ -12,7 +12,7 @@ scale we target (up to 100K facts). At that scale, a single cosine
12
12
  scan takes ~5ms on CPU which is well within our latency budget.
13
13
 
14
14
  Part of Qualixar | Author: Varun Pratap Bhardwaj
15
- License: Elastic-2.0
15
+ License: AGPL-3.0-or-later
16
16
  """
17
17
 
18
18
  from __future__ import annotations
@@ -12,7 +12,7 @@ the entire index. This version persists tokens to the DB via
12
12
  store_bm25_tokens / get_all_bm25_tokens and cold-loads on init.
13
13
 
14
14
  Part of Qualixar | Author: Varun Pratap Bhardwaj
15
- License: Elastic-2.0
15
+ License: AGPL-3.0-or-later
16
16
  """
17
17
 
18
18
  from __future__ import annotations
@@ -23,6 +23,8 @@ from typing import TYPE_CHECKING
23
23
 
24
24
  from rank_bm25 import BM25Plus
25
25
 
26
+ from superlocalmemory.storage.database import _scope_where
27
+
26
28
  if TYPE_CHECKING:
27
29
  from superlocalmemory.storage.database import DatabaseManager
28
30
 
@@ -72,6 +74,7 @@ class BM25Channel:
72
74
  self._bm25: BM25Plus | None = None
73
75
  self._dirty: bool = False
74
76
  self._loaded_profiles: set[str] = set()
77
+ self._loaded_scope_key: tuple[str, bool, bool] | None = None
75
78
 
76
79
  @property
77
80
  def document_count(self) -> int:
@@ -82,18 +85,31 @@ class BM25Channel:
82
85
 
83
86
  Idempotent: subsequent calls for the same profile are no-ops.
84
87
  """
85
- if profile_id in self._loaded_profiles:
88
+ include_global = bool(getattr(self, "include_global", False))
89
+ include_shared = bool(getattr(self, "include_shared", False))
90
+ scope_key = (profile_id, include_global, include_shared)
91
+ if scope_key == self._loaded_scope_key:
86
92
  return
87
93
 
88
- token_map = self._db.get_all_bm25_tokens(profile_id)
89
- _inc_global = getattr(self, 'include_global', False)
90
- _inc_shared = getattr(self, 'include_shared', False)
94
+ # A legacy in-memory index is a visibility-specific view. Reusing a
95
+ # corpus warmed under another profile/scope leaks private documents or
96
+ # omits newly enabled global/shared documents.
97
+ self._corpus = []
98
+ self._fact_ids = []
99
+ self._fact_id_set = set()
100
+ self._raw_texts = []
101
+ self._bm25 = None
102
+ token_map = self._db.get_all_bm25_tokens(
103
+ profile_id,
104
+ include_global=include_global,
105
+ include_shared=include_shared,
106
+ )
91
107
  if not token_map:
92
108
  # Fallback: tokenize facts directly if no pre-stored tokens
93
109
  facts = self._db.get_all_facts(
94
110
  profile_id,
95
- include_global=_inc_global,
96
- include_shared=_inc_shared,
111
+ include_global=include_global,
112
+ include_shared=include_shared,
97
113
  )
98
114
  for fact in facts:
99
115
  if fact.fact_id in self._fact_id_set:
@@ -112,8 +128,8 @@ class BM25Channel:
112
128
  try:
113
129
  facts = self._db.get_all_facts(
114
130
  profile_id,
115
- include_global=_inc_global,
116
- include_shared=_inc_shared,
131
+ include_global=include_global,
132
+ include_shared=include_shared,
117
133
  )
118
134
  fact_content_map = {f.fact_id: f.content for f in facts}
119
135
  except Exception:
@@ -128,6 +144,7 @@ class BM25Channel:
128
144
 
129
145
  self._dirty = True
130
146
  self._loaded_profiles.add(profile_id)
147
+ self._loaded_scope_key = scope_key
131
148
  logger.debug(
132
149
  "BM25 cold-loaded %d documents for profile=%s",
133
150
  len(token_map) if token_map else 0, profile_id,
@@ -176,14 +193,20 @@ class BM25Channel:
176
193
  # Quote each token so query punctuation can't break FTS5 MATCH syntax;
177
194
  # OR-join for high recall (any token may match).
178
195
  match_expr = " OR ".join('"' + t.replace('"', "") + '"' for t in tokens)
196
+ where, params = _scope_where(
197
+ profile_id,
198
+ include_global=bool(getattr(self, "include_global", False)),
199
+ include_shared=bool(getattr(self, "include_shared", False)),
200
+ prefix="af",
201
+ )
179
202
  sql = (
180
203
  "SELECT af.fact_id AS fact_id, bm25(atomic_facts_fts) AS rank "
181
204
  "FROM atomic_facts_fts "
182
205
  "JOIN atomic_facts af ON af.rowid = atomic_facts_fts.rowid "
183
- "WHERE atomic_facts_fts MATCH ? AND af.profile_id = ? "
206
+ f"WHERE atomic_facts_fts MATCH ? AND {where} "
184
207
  "ORDER BY rank LIMIT ?"
185
208
  )
186
- rows = self._db.execute(sql, (match_expr, profile_id, int(top_k)))
209
+ rows = self._db.execute(sql, (match_expr, *params, int(top_k)))
187
210
  out: list[tuple[str, float]] = []
188
211
  for r in rows:
189
212
  d = dict(r)
@@ -263,3 +286,4 @@ class BM25Channel:
263
286
  self._bm25 = None
264
287
  self._dirty = False
265
288
  self._loaded_profiles = set()
289
+ self._loaded_scope_key = None
@@ -22,14 +22,18 @@ Parameters:
22
22
  - typed mu: entity=1.2, causal=1.3, semantic=0.8, temporal=0.9
23
23
 
24
24
  Part of Qualixar | Author: Varun Pratap Bhardwaj
25
- License: Elastic-2.0
25
+ License: AGPL-3.0-or-later
26
26
  """
27
27
  from __future__ import annotations
28
28
 
29
29
  import logging
30
- import re
31
30
  from typing import TYPE_CHECKING
32
31
 
32
+ from superlocalmemory.retrieval.scope_policy import (
33
+ authorized_fact_ids,
34
+ filter_authorized_results,
35
+ )
36
+
33
37
  if TYPE_CHECKING:
34
38
  from superlocalmemory.storage.database import DatabaseManager
35
39
 
@@ -64,6 +68,9 @@ class BridgeDiscovery:
64
68
  seed_ids: list[str],
65
69
  profile_id: str,
66
70
  max_bridges: int = 10,
71
+ *,
72
+ include_global: bool = False,
73
+ include_shared: bool = False,
67
74
  ) -> list[tuple[str, float]]:
68
75
  """Find bridge facts connecting seed results.
69
76
 
@@ -78,13 +85,36 @@ class BridgeDiscovery:
78
85
  if len(seed_ids) < 2:
79
86
  return []
80
87
 
88
+ allowed_seeds = authorized_fact_ids(
89
+ self._db,
90
+ seed_ids,
91
+ profile_id,
92
+ include_global=include_global,
93
+ include_shared=include_shared,
94
+ )
95
+ if any(seed_id not in allowed_seeds for seed_id in seed_ids):
96
+ return []
97
+
98
+ try:
99
+ visible_seed_facts = self._db.get_facts_by_ids(
100
+ seed_ids,
101
+ profile_id,
102
+ include_global=include_global,
103
+ include_shared=include_shared,
104
+ )
105
+ except Exception:
106
+ return []
107
+ seed_facts = {fact.fact_id: fact for fact in visible_seed_facts}
108
+ if any(seed_id not in seed_facts for seed_id in seed_ids):
109
+ return []
110
+
81
111
  bridges: list[tuple[str, float]] = []
82
112
  seen = set(seed_ids)
83
113
 
84
114
  # Check consecutive pairs for entity overlap
85
115
  for i in range(len(seed_ids) - 1):
86
- fact_a = self._db.get_fact(seed_ids[i])
87
- fact_b = self._db.get_fact(seed_ids[i + 1])
116
+ fact_a = seed_facts.get(seed_ids[i])
117
+ fact_b = seed_facts.get(seed_ids[i + 1])
88
118
  if not fact_a or not fact_b:
89
119
  continue
90
120
 
@@ -98,7 +128,12 @@ class BridgeDiscovery:
98
128
  # Strategy 1: Entity bridge (union minus intersection)
99
129
  bridge_entities = (entities_a | entities_b) - (entities_a & entities_b)
100
130
  for eid in bridge_entities:
101
- entity_facts = self._db.get_facts_by_entity(eid, profile_id)
131
+ entity_facts = self._db.get_facts_by_entity(
132
+ eid,
133
+ profile_id,
134
+ include_global=include_global,
135
+ include_shared=include_shared,
136
+ )
102
137
  for f in entity_facts[:5]:
103
138
  if f.fact_id not in seen:
104
139
  seen.add(f.fact_id)
@@ -112,7 +147,13 @@ class BridgeDiscovery:
112
147
  break
113
148
 
114
149
  bridges.sort(key=lambda x: x[1], reverse=True)
115
- return bridges[:max_bridges]
150
+ return filter_authorized_results(
151
+ self._db,
152
+ bridges,
153
+ profile_id,
154
+ include_global=include_global,
155
+ include_shared=include_shared,
156
+ )[:max_bridges]
116
157
 
117
158
  def spreading_activation(
118
159
  self,
@@ -120,6 +161,9 @@ class BridgeDiscovery:
120
161
  profile_id: str,
121
162
  max_depth: int = _MAX_DEPTH,
122
163
  budget: int = _NODE_BUDGET,
164
+ *,
165
+ include_global: bool = False,
166
+ include_shared: bool = False,
123
167
  ) -> list[tuple[str, float]]:
124
168
  """Spreading activation from seed facts through the graph.
125
169
 
@@ -135,6 +179,16 @@ class BridgeDiscovery:
135
179
  Returns:
136
180
  List of (fact_id, activation_score) for activated facts.
137
181
  """
182
+ allowed_seeds = authorized_fact_ids(
183
+ self._db,
184
+ seed_ids,
185
+ profile_id,
186
+ include_global=include_global,
187
+ include_shared=include_shared,
188
+ )
189
+ if any(seed_id not in allowed_seeds for seed_id in seed_ids):
190
+ return []
191
+
138
192
  activations: dict[str, float] = {fid: 1.0 for fid in seed_ids}
139
193
  frontier = list(seed_ids)
140
194
 
@@ -145,7 +199,12 @@ class BridgeDiscovery:
145
199
  if current_activation < 0.01:
146
200
  continue
147
201
 
148
- edges = self._db.get_edges_for_node(fid, profile_id)
202
+ edges = self._db.get_edges_for_node(
203
+ fid,
204
+ profile_id,
205
+ include_global=include_global,
206
+ include_shared=include_shared,
207
+ )
149
208
  for edge in edges:
150
209
  other_id = (
151
210
  edge.target_id
@@ -171,4 +230,10 @@ class BridgeDiscovery:
171
230
  if fid not in set(seed_ids) and score > 0.01
172
231
  ]
173
232
  results.sort(key=lambda x: x[1], reverse=True)
174
- return results[:budget]
233
+ return filter_authorized_results(
234
+ self._db,
235
+ results,
236
+ profile_id,
237
+ include_global=include_global,
238
+ include_shared=include_shared,
239
+ )[:budget]