superlocalmemory 3.4.18 → 3.4.21

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 (172) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +42 -34
  3. package/bin/slm +11 -0
  4. package/bin/slm.bat +12 -0
  5. package/package.json +4 -3
  6. package/pyproject.toml +3 -2
  7. package/scripts/build-slm-hook.ps1 +40 -0
  8. package/scripts/build-slm-hook.sh +45 -0
  9. package/scripts/build_entry.py +452 -0
  10. package/scripts/ci/stage5b_gate.sh +50 -0
  11. package/scripts/postinstall/validation.js +187 -0
  12. package/scripts/postinstall-interactive.js +756 -0
  13. package/scripts/postinstall_binary.js +287 -0
  14. package/scripts/release_manifest.py +273 -0
  15. package/scripts/slm-hook.spec +56 -0
  16. package/skills/slm-build-graph/SKILL.md +423 -0
  17. package/skills/slm-list-recent/SKILL.md +348 -0
  18. package/skills/slm-recall/SKILL.md +343 -0
  19. package/skills/slm-remember/SKILL.md +194 -0
  20. package/skills/slm-show-patterns/SKILL.md +224 -0
  21. package/skills/slm-status/SKILL.md +363 -0
  22. package/skills/slm-switch-profile/SKILL.md +442 -0
  23. package/src/superlocalmemory/cli/commands.py +219 -79
  24. package/src/superlocalmemory/cli/context_commands.py +192 -0
  25. package/src/superlocalmemory/cli/daemon.py +15 -1
  26. package/src/superlocalmemory/cli/db_migrate.py +80 -0
  27. package/src/superlocalmemory/cli/escape_hatch.py +220 -0
  28. package/src/superlocalmemory/cli/main.py +72 -1
  29. package/src/superlocalmemory/core/context_cache.py +397 -0
  30. package/src/superlocalmemory/core/embeddings.py +8 -2
  31. package/src/superlocalmemory/core/engine.py +38 -2
  32. package/src/superlocalmemory/core/engine_wiring.py +1 -1
  33. package/src/superlocalmemory/core/ram_lock.py +111 -0
  34. package/src/superlocalmemory/core/recall_pipeline.py +433 -3
  35. package/src/superlocalmemory/core/recall_worker.py +8 -3
  36. package/src/superlocalmemory/core/security_primitives.py +635 -0
  37. package/src/superlocalmemory/core/shadow_router.py +319 -0
  38. package/src/superlocalmemory/core/slm_disabled.py +87 -0
  39. package/src/superlocalmemory/core/slmignore.py +125 -0
  40. package/src/superlocalmemory/core/topic_signature.py +143 -0
  41. package/src/superlocalmemory/core/worker_pool.py +14 -3
  42. package/src/superlocalmemory/encoding/cognitive_consolidator.py +2 -2
  43. package/src/superlocalmemory/evolution/budget.py +321 -0
  44. package/src/superlocalmemory/evolution/llm_dispatch.py +508 -0
  45. package/src/superlocalmemory/evolution/skill_evolver.py +144 -94
  46. package/src/superlocalmemory/hooks/_outcome_common.py +506 -0
  47. package/src/superlocalmemory/hooks/adapter_base.py +317 -0
  48. package/src/superlocalmemory/hooks/antigravity_adapter.py +192 -0
  49. package/src/superlocalmemory/hooks/claude_code_hooks.py +33 -1
  50. package/src/superlocalmemory/hooks/context_payload.py +312 -0
  51. package/src/superlocalmemory/hooks/copilot_adapter.py +154 -0
  52. package/src/superlocalmemory/hooks/cross_platform_connector.py +90 -0
  53. package/src/superlocalmemory/hooks/cursor_adapter.py +195 -0
  54. package/src/superlocalmemory/hooks/hook_handlers.py +109 -8
  55. package/src/superlocalmemory/hooks/ide_connector.py +25 -2
  56. package/src/superlocalmemory/hooks/post_tool_async_hook.py +165 -0
  57. package/src/superlocalmemory/hooks/post_tool_outcome_hook.py +223 -0
  58. package/src/superlocalmemory/hooks/prewarm_auth.py +170 -0
  59. package/src/superlocalmemory/hooks/session_registry.py +186 -0
  60. package/src/superlocalmemory/hooks/stop_outcome_hook.py +134 -0
  61. package/src/superlocalmemory/hooks/sync_loop.py +114 -0
  62. package/src/superlocalmemory/hooks/user_prompt_hook.py +128 -0
  63. package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +202 -0
  64. package/src/superlocalmemory/infra/backup.py +3 -3
  65. package/src/superlocalmemory/infra/cloud_backup.py +2 -2
  66. package/src/superlocalmemory/infra/event_bus.py +2 -2
  67. package/src/superlocalmemory/infra/webhook_dispatcher.py +3 -3
  68. package/src/superlocalmemory/learning/arm_catalog.py +99 -0
  69. package/src/superlocalmemory/learning/bandit.py +526 -0
  70. package/src/superlocalmemory/learning/bandit_cache.py +133 -0
  71. package/src/superlocalmemory/learning/behavioral.py +53 -1
  72. package/src/superlocalmemory/learning/consolidation_cycle.py +381 -0
  73. package/src/superlocalmemory/learning/consolidation_worker.py +188 -520
  74. package/src/superlocalmemory/learning/database.py +256 -0
  75. package/src/superlocalmemory/learning/dedup_hnsw.py +413 -0
  76. package/src/superlocalmemory/learning/ensemble.py +300 -0
  77. package/src/superlocalmemory/learning/fact_outcome_joins.py +207 -0
  78. package/src/superlocalmemory/learning/forgetting_scheduler.py +55 -0
  79. package/src/superlocalmemory/learning/hnsw_dedup.py +69 -0
  80. package/src/superlocalmemory/learning/labeler.py +87 -0
  81. package/src/superlocalmemory/learning/legacy_migration.py +277 -0
  82. package/src/superlocalmemory/learning/memory_merge.py +160 -0
  83. package/src/superlocalmemory/learning/model_cache.py +269 -0
  84. package/src/superlocalmemory/learning/model_rollback.py +278 -0
  85. package/src/superlocalmemory/learning/outcome_queue.py +284 -0
  86. package/src/superlocalmemory/learning/pattern_miner.py +415 -0
  87. package/src/superlocalmemory/learning/pattern_miner_constants.py +47 -0
  88. package/src/superlocalmemory/learning/ranker.py +225 -81
  89. package/src/superlocalmemory/learning/ranker_common.py +163 -0
  90. package/src/superlocalmemory/learning/ranker_retrain_legacy.py +202 -0
  91. package/src/superlocalmemory/learning/ranker_retrain_online.py +411 -0
  92. package/src/superlocalmemory/learning/reward.py +777 -0
  93. package/src/superlocalmemory/learning/reward_archive.py +210 -0
  94. package/src/superlocalmemory/learning/reward_boost.py +201 -0
  95. package/src/superlocalmemory/learning/reward_proxy.py +326 -0
  96. package/src/superlocalmemory/learning/shadow_test.py +524 -0
  97. package/src/superlocalmemory/learning/signal_worker.py +270 -0
  98. package/src/superlocalmemory/learning/signals.py +314 -0
  99. package/src/superlocalmemory/learning/trigram_index.py +547 -0
  100. package/src/superlocalmemory/mcp/server.py +5 -5
  101. package/src/superlocalmemory/mcp/tools_context.py +183 -0
  102. package/src/superlocalmemory/mcp/tools_core.py +92 -27
  103. package/src/superlocalmemory/parameterization/soft_prompt_generator.py +13 -0
  104. package/src/superlocalmemory/retrieval/engine.py +52 -0
  105. package/src/superlocalmemory/retrieval/reranker.py +4 -2
  106. package/src/superlocalmemory/server/api.py +2 -2
  107. package/src/superlocalmemory/server/bandit_loops.py +140 -0
  108. package/src/superlocalmemory/server/middleware/__init__.py +11 -0
  109. package/src/superlocalmemory/server/middleware/security_headers.py +144 -0
  110. package/src/superlocalmemory/server/routes/backup.py +36 -13
  111. package/src/superlocalmemory/server/routes/behavioral.py +50 -19
  112. package/src/superlocalmemory/server/routes/brain.py +1234 -0
  113. package/src/superlocalmemory/server/routes/data_io.py +4 -4
  114. package/src/superlocalmemory/server/routes/events.py +2 -2
  115. package/src/superlocalmemory/server/routes/helpers.py +1 -1
  116. package/src/superlocalmemory/server/routes/learning.py +192 -7
  117. package/src/superlocalmemory/server/routes/memories.py +189 -1
  118. package/src/superlocalmemory/server/routes/prewarm.py +171 -0
  119. package/src/superlocalmemory/server/routes/profiles.py +3 -3
  120. package/src/superlocalmemory/server/routes/token.py +88 -0
  121. package/src/superlocalmemory/server/routes/ws.py +5 -5
  122. package/src/superlocalmemory/server/security_middleware.py +13 -7
  123. package/src/superlocalmemory/server/ui.py +2 -2
  124. package/src/superlocalmemory/server/unified_daemon.py +335 -3
  125. package/src/superlocalmemory/storage/migration_runner.py +545 -0
  126. package/src/superlocalmemory/storage/migrations/M001_add_signal_features_columns.py +67 -0
  127. package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +132 -0
  128. package/src/superlocalmemory/storage/migrations/M003_migration_log.py +38 -0
  129. package/src/superlocalmemory/storage/migrations/M004_cross_platform_sync_log.py +46 -0
  130. package/src/superlocalmemory/storage/migrations/M005_bandit_tables.py +75 -0
  131. package/src/superlocalmemory/storage/migrations/M006_action_outcomes_reward.py +75 -0
  132. package/src/superlocalmemory/storage/migrations/M007_pending_outcomes.py +63 -0
  133. package/src/superlocalmemory/storage/migrations/M009_model_lineage.py +54 -0
  134. package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +75 -0
  135. package/src/superlocalmemory/storage/migrations/M011_archive_and_merge.py +87 -0
  136. package/src/superlocalmemory/storage/migrations/M012_shadow_observations.py +72 -0
  137. package/src/superlocalmemory/storage/migrations/M013_bi_temporal_columns.py +55 -0
  138. package/src/superlocalmemory/storage/migrations/__init__.py +81 -0
  139. package/src/superlocalmemory/storage/models.py +4 -0
  140. package/src/superlocalmemory/ui/css/brain.css +409 -0
  141. package/src/superlocalmemory/ui/css/legacy-dashboard.css +645 -0
  142. package/src/superlocalmemory/ui/index.html +459 -1345
  143. package/src/superlocalmemory/ui/js/brain.js +1321 -0
  144. package/src/superlocalmemory/ui/js/clusters.js +123 -4
  145. package/src/superlocalmemory/ui/js/init.js +48 -39
  146. package/src/superlocalmemory/ui/js/memories.js +88 -2
  147. package/src/superlocalmemory/ui/js/modal.js +71 -1
  148. package/src/superlocalmemory/ui/js/ng-shell.js +101 -88
  149. package/src/superlocalmemory/ui/js/trust-dashboard.js +168 -25
  150. package/src/superlocalmemory/ui/vendor/bootstrap-icons/bootstrap-icons.css +2018 -0
  151. package/src/superlocalmemory/ui/vendor/bootstrap-icons/fonts/bootstrap-icons.woff +0 -0
  152. package/src/superlocalmemory/ui/vendor/bootstrap-icons/fonts/bootstrap-icons.woff2 +0 -0
  153. package/src/superlocalmemory/ui/vendor/bootstrap.bundle.min.js +7 -0
  154. package/src/superlocalmemory/ui/vendor/bootstrap.min.css +6 -0
  155. package/src/superlocalmemory/ui/vendor/d3.v7.min.js +2 -0
  156. package/src/superlocalmemory/ui/vendor/graphology-library.min.js +2 -0
  157. package/src/superlocalmemory/ui/vendor/graphology.umd.min.js +2 -0
  158. package/src/superlocalmemory/ui/vendor/inter-ui/inter-variable.min.css +8 -0
  159. package/src/superlocalmemory/ui/vendor/inter-ui/variable/InterVariable-Italic.woff2 +0 -0
  160. package/src/superlocalmemory/ui/vendor/inter-ui/variable/InterVariable.woff2 +0 -0
  161. package/src/superlocalmemory/ui/vendor/sigma.min.js +1 -0
  162. package/src/superlocalmemory/ui/js/behavioral.js +0 -447
  163. package/src/superlocalmemory/ui/js/graph-core.js +0 -447
  164. package/src/superlocalmemory/ui/js/graph-interactions.js +0 -351
  165. package/src/superlocalmemory/ui/js/learning.js +0 -435
  166. package/src/superlocalmemory/ui/js/patterns.js +0 -93
  167. package/src/superlocalmemory.egg-info/PKG-INFO +0 -647
  168. package/src/superlocalmemory.egg-info/SOURCES.txt +0 -335
  169. package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
  170. package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
  171. package/src/superlocalmemory.egg-info/requires.txt +0 -58
  172. package/src/superlocalmemory.egg-info/top_level.txt +0 -1
@@ -12,10 +12,55 @@ Part of Qualixar | Author: Varun Pratap Bhardwaj
12
12
 
13
13
  from __future__ import annotations
14
14
 
15
+ import os
15
16
  import sys
16
17
  from argparse import Namespace
17
18
 
18
19
 
20
+ def _cmd_db_dispatch(args: Namespace) -> None:
21
+ """Route ``slm db ...`` subcommands. LLD-06 §7.2."""
22
+ sub = getattr(args, "db_command", None)
23
+ if sub == "migrate":
24
+ from superlocalmemory.cli.db_migrate import cmd_db_migrate
25
+ rc = cmd_db_migrate(args)
26
+ if rc:
27
+ sys.exit(rc)
28
+ return
29
+ print("Usage: slm db migrate [--status] [--dry-run]")
30
+ sys.exit(2)
31
+
32
+
33
+ def _cmd_escape_disable(args: Namespace) -> None:
34
+ from superlocalmemory.cli.escape_hatch import cmd_disable
35
+ cmd_disable(args)
36
+
37
+
38
+ def _cmd_escape_enable(args: Namespace) -> None:
39
+ from superlocalmemory.cli.escape_hatch import cmd_enable
40
+ cmd_enable(args)
41
+
42
+
43
+ def _cmd_escape_clear_cache(args: Namespace) -> None:
44
+ from superlocalmemory.cli.escape_hatch import cmd_clear_cache
45
+ cmd_clear_cache(args)
46
+
47
+
48
+ def _cmd_escape_reconfigure(args: Namespace) -> None:
49
+ from superlocalmemory.cli.escape_hatch import cmd_reconfigure
50
+ cmd_reconfigure(args)
51
+
52
+
53
+ def _cmd_escape_benchmark(args: Namespace) -> None:
54
+ from superlocalmemory.cli.escape_hatch import cmd_benchmark
55
+ cmd_benchmark(args)
56
+
57
+
58
+ def _cmd_escape_rotate_token(args: Namespace) -> None:
59
+ """S-M07: rotate the install token."""
60
+ from superlocalmemory.cli.escape_hatch import cmd_rotate_token
61
+ cmd_rotate_token(args)
62
+
63
+
19
64
  def dispatch(args: Namespace) -> None:
20
65
  """Route CLI command to the appropriate handler."""
21
66
  # Auto-install/upgrade hooks on version change (single file read, ~0.1ms)
@@ -67,6 +112,17 @@ def dispatch(args: Namespace) -> None:
67
112
  # V3.4.11 skill evolution
68
113
  "config": cmd_config,
69
114
  "evolve": cmd_evolve,
115
+ # V3.4.21 LLD-05 context pre-staging
116
+ "context": _cmd_context_dispatch,
117
+ # V3.4.21 LLD-06 additive schema migrations
118
+ "db": _cmd_db_dispatch,
119
+ # V3.4.21 Stage 8 SB-5 — MASTER-PLAN §8 escape hatches.
120
+ "disable": _cmd_escape_disable,
121
+ "enable": _cmd_escape_enable,
122
+ "clear-cache": _cmd_escape_clear_cache,
123
+ "reconfigure": _cmd_escape_reconfigure,
124
+ "benchmark": _cmd_escape_benchmark,
125
+ "rotate-token": _cmd_escape_rotate_token,
70
126
  }
71
127
  handler = handlers.get(args.command)
72
128
  if handler:
@@ -633,8 +689,21 @@ def cmd_provider(args: Namespace) -> None:
633
689
  print(f"Model: {config.llm.model}")
634
690
 
635
691
 
692
+ def _cmd_context_dispatch(args: Namespace) -> None:
693
+ """V3.4.21 LLD-05: ``slm context prestage``."""
694
+ from superlocalmemory.cli.context_commands import cmd_context
695
+ cmd_context(args)
696
+
697
+
636
698
  def cmd_connect(args: Namespace) -> None:
637
- """Configure IDE integrations."""
699
+ """Configure IDE integrations. V3.4.21: ``--cross-platform`` uses LLD-05."""
700
+ # Route --disable <name> and --cross-platform to the LLD-05 orchestrator.
701
+ if getattr(args, "disable", None) or getattr(args, "cross_platform", False):
702
+ from superlocalmemory.cli.context_commands import (
703
+ cmd_connect_cross_platform,
704
+ )
705
+ cmd_connect_cross_platform(args)
706
+ return
638
707
  from superlocalmemory.hooks.ide_connector import IDEConnector
639
708
 
640
709
  connector = IDEConnector()
@@ -824,12 +893,19 @@ def cmd_recall(args: Namespace) -> None:
824
893
 
825
894
  # V3.3.21: Route through daemon for instant response (no cold start).
826
895
  # Falls back to direct engine if daemon not running.
896
+ # S9-DASH-02: pass a stable session_id derived from the shell's
897
+ # parent PID so a sequence of CLI recalls in one terminal can be
898
+ # grouped. The Stop hook on session end won't fire for CLI, so
899
+ # these outcomes are closed by the reaper (TTL → neutral reward).
827
900
  try:
828
901
  from superlocalmemory.cli.daemon import is_daemon_running, daemon_request, ensure_daemon
829
902
  if is_daemon_running() or ensure_daemon():
830
903
  from urllib.parse import quote
904
+ session_id = f"cli:{os.getppid()}"
831
905
  result = daemon_request(
832
- "GET", f"/recall?q={quote(args.query)}&limit={args.limit}",
906
+ "GET",
907
+ f"/recall?q={quote(args.query)}&limit={args.limit}"
908
+ f"&session_id={quote(session_id)}",
833
909
  )
834
910
  if result and "results" in result:
835
911
  # Format daemon response same as engine response
@@ -1156,6 +1232,30 @@ def cmd_status(args: Namespace) -> None:
1156
1232
  size_mb = round(config.db_path.stat().st_size / 1024 / 1024, 2)
1157
1233
  print(f" DB size: {size_mb} MB")
1158
1234
 
1235
+ # S9-UX-07 / S9-UX-13: --verbose surfaces the disabled marker,
1236
+ # last-version marker, and daemon port so users who are debugging
1237
+ # "slm seems broken" get the signal without opening three files.
1238
+ if getattr(args, "verbose", False):
1239
+ home = config.base_dir
1240
+ disabled_path = home / ".disabled"
1241
+ version_path = home / ".last_version"
1242
+ print()
1243
+ print(" --verbose --")
1244
+ print(
1245
+ f" Disabled marker: "
1246
+ f"{'YES (slm enable to reactivate)' if disabled_path.exists() else 'no'}",
1247
+ )
1248
+ if version_path.exists():
1249
+ try:
1250
+ last_ver = version_path.read_text(encoding="utf-8").strip()
1251
+ except OSError:
1252
+ last_ver = "(unreadable)"
1253
+ print(f" Last booted version: {last_ver}")
1254
+ else:
1255
+ print(" Last booted version: (never booted)")
1256
+ port = os.environ.get("SLM_DAEMON_PORT") or "8765"
1257
+ print(f" Daemon port: {port}")
1258
+
1159
1259
 
1160
1260
  def cmd_health(args: Namespace) -> None:
1161
1261
  """Show math layer health status."""
@@ -1198,11 +1298,18 @@ def cmd_health(args: Namespace) -> None:
1198
1298
 
1199
1299
 
1200
1300
  def cmd_doctor(args: Namespace) -> None:
1201
- """Comprehensive pre-flight check — verify everything works."""
1301
+ """Comprehensive pre-flight check — verify everything works.
1302
+
1303
+ S9-UX-10: ``--quick`` skips the slow probes (embedding worker
1304
+ subprocess, Ollama roundtrip) so first-run installer hooks can
1305
+ surface a sub-second PASS/FAIL line before letting the user go.
1306
+ Full doctor remains the default for ``slm doctor`` with no flag.
1307
+ """
1202
1308
  import shutil
1203
1309
  from pathlib import Path
1204
1310
 
1205
1311
  use_json = getattr(args, "json", False)
1312
+ quick = getattr(args, "quick", False)
1206
1313
  checks: list[dict] = []
1207
1314
  passed = warned = failed = 0
1208
1315
 
@@ -1315,86 +1422,119 @@ def cmd_doctor(args: Namespace) -> None:
1315
1422
  _check("Performance deps", "WARN", f"Missing: {', '.join(missing)}",
1316
1423
  "pip install diskcache orjson")
1317
1424
 
1318
- # 7. Embedding worker functional test
1319
- try:
1320
- import subprocess as _sp
1321
- import json as _json
1322
-
1323
- env = {
1324
- **__import__("os").environ,
1325
- "CUDA_VISIBLE_DEVICES": "",
1326
- "PYTORCH_MPS_HIGH_WATERMARK_RATIO": "0.0",
1327
- "TOKENIZERS_PARALLELISM": "false",
1328
- "TORCH_DEVICE": "cpu",
1329
- }
1330
- proc = _sp.Popen(
1331
- [sys.executable, "-m", "superlocalmemory.core.embedding_worker"],
1332
- stdin=_sp.PIPE, stdout=_sp.PIPE, stderr=_sp.DEVNULL,
1333
- text=True, bufsize=1, env=env,
1334
- )
1335
- proc.stdin.write(_json.dumps({"cmd": "ping"}) + "\n")
1336
- proc.stdin.flush()
1337
-
1338
- import select as _sel
1339
- ready, _, _ = _sel.select([proc.stdout], [], [], 30)
1340
- if ready:
1341
- resp = _json.loads(proc.stdout.readline())
1342
- if resp.get("ok"):
1343
- _check("Embedding worker", "PASS",
1344
- f"responsive (PID {proc.pid}, Python {sys.executable})")
1425
+ # 7. Embedding worker functional test — skipped under --quick.
1426
+ if quick:
1427
+ _check("Embedding worker", "PASS", "skipped (--quick)")
1428
+ else:
1429
+ try:
1430
+ import subprocess as _sp
1431
+ import json as _json
1432
+
1433
+ env = {
1434
+ **__import__("os").environ,
1435
+ "CUDA_VISIBLE_DEVICES": "",
1436
+ "PYTORCH_MPS_HIGH_WATERMARK_RATIO": "0.0",
1437
+ "TOKENIZERS_PARALLELISM": "false",
1438
+ "TORCH_DEVICE": "cpu",
1439
+ }
1440
+ proc = _sp.Popen(
1441
+ [sys.executable, "-m",
1442
+ "superlocalmemory.core.embedding_worker"],
1443
+ stdin=_sp.PIPE, stdout=_sp.PIPE, stderr=_sp.DEVNULL,
1444
+ text=True, bufsize=1, env=env,
1445
+ )
1446
+ proc.stdin.write(_json.dumps({"cmd": "ping"}) + "\n")
1447
+ proc.stdin.flush()
1448
+
1449
+ import select as _sel
1450
+ ready, _, _ = _sel.select([proc.stdout], [], [], 30)
1451
+ if ready:
1452
+ resp = _json.loads(proc.stdout.readline())
1453
+ if resp.get("ok"):
1454
+ _check(
1455
+ "Embedding worker", "PASS",
1456
+ f"responsive (PID {proc.pid}, "
1457
+ f"Python {sys.executable})",
1458
+ )
1459
+ else:
1460
+ _check(
1461
+ "Embedding worker", "FAIL",
1462
+ f"error: {resp.get('error', 'unknown')}",
1463
+ "pip install sentence-transformers einops torch",
1464
+ )
1345
1465
  else:
1346
- _check("Embedding worker", "FAIL",
1347
- f"error: {resp.get('error', 'unknown')}",
1348
- "pip install sentence-transformers einops torch")
1349
- else:
1350
- _check("Embedding worker", "FAIL", "timed out (30s)",
1351
- "slm warmup")
1352
- proc.stdin.write(_json.dumps({"cmd": "quit"}) + "\n")
1353
- proc.stdin.flush()
1354
- proc.wait(timeout=5)
1355
- except FileNotFoundError:
1356
- _check("Embedding worker", "FAIL", "embedding_worker module not found",
1357
- "Reinstall: npm install -g superlocalmemory")
1358
- except Exception as exc:
1359
- _check("Embedding worker", "FAIL", str(exc),
1360
- "slm warmup")
1466
+ _check("Embedding worker", "FAIL", "timed out (30s)",
1467
+ "slm warmup")
1468
+ proc.stdin.write(_json.dumps({"cmd": "quit"}) + "\n")
1469
+ proc.stdin.flush()
1470
+ proc.wait(timeout=5)
1471
+ except FileNotFoundError:
1472
+ _check(
1473
+ "Embedding worker", "FAIL",
1474
+ "embedding_worker module not found",
1475
+ "Reinstall: npm install -g superlocalmemory",
1476
+ )
1477
+ except Exception as exc:
1478
+ _check("Embedding worker", "FAIL", str(exc), "slm warmup")
1361
1479
 
1362
- # 8. Ollama connectivity (Mode B only)
1363
- try:
1364
- from superlocalmemory.core.config import SLMConfig
1365
- config = SLMConfig.load()
1366
- if config.mode.value == "b":
1367
- import httpx
1368
- try:
1369
- resp = httpx.get(
1370
- f"{config.llm.api_base}/api/tags", timeout=5.0,
1371
- )
1372
- if resp.status_code == 200:
1373
- models = [m["name"].split(":")[0] for m in resp.json().get("models", [])]
1374
- has_llm = config.llm.model.split(":")[0] in models
1375
- if has_llm:
1376
- _check("Ollama", "PASS",
1377
- f"running, {len(models)} models, '{config.llm.model}' available")
1480
+ # 8. Ollama connectivity (Mode B only) — skipped under --quick.
1481
+ if quick:
1482
+ _check("Ollama / API key", "PASS", "skipped (--quick)")
1483
+ else:
1484
+ try:
1485
+ from superlocalmemory.core.config import SLMConfig
1486
+ config = SLMConfig.load()
1487
+ if config.mode.value == "b":
1488
+ import httpx
1489
+ try:
1490
+ resp = httpx.get(
1491
+ f"{config.llm.api_base}/api/tags", timeout=5.0,
1492
+ )
1493
+ if resp.status_code == 200:
1494
+ models = [
1495
+ m["name"].split(":")[0]
1496
+ for m in resp.json().get("models", [])
1497
+ ]
1498
+ has_llm = config.llm.model.split(":")[0] in models
1499
+ if has_llm:
1500
+ _check(
1501
+ "Ollama", "PASS",
1502
+ f"running, {len(models)} models, "
1503
+ f"'{config.llm.model}' available",
1504
+ )
1505
+ else:
1506
+ _check(
1507
+ "Ollama", "WARN",
1508
+ f"running but '{config.llm.model}' "
1509
+ f"not pulled",
1510
+ f"ollama pull {config.llm.model}",
1511
+ )
1378
1512
  else:
1379
- _check("Ollama", "WARN",
1380
- f"running but '{config.llm.model}' not pulled",
1381
- f"ollama pull {config.llm.model}")
1513
+ _check(
1514
+ "Ollama", "WARN",
1515
+ f"HTTP {resp.status_code}",
1516
+ "brew services start ollama",
1517
+ )
1518
+ except Exception:
1519
+ _check(
1520
+ "Ollama", "WARN",
1521
+ "not reachable at " + config.llm.api_base,
1522
+ "brew services start ollama",
1523
+ )
1524
+ elif config.mode.value == "c":
1525
+ if config.llm.api_key:
1526
+ _check(
1527
+ "API key", "PASS",
1528
+ f"provider={config.llm.provider}, "
1529
+ f"key=***{config.llm.api_key[-4:]}",
1530
+ )
1382
1531
  else:
1383
- _check("Ollama", "WARN", f"HTTP {resp.status_code}",
1384
- "brew services start ollama")
1385
- except Exception:
1386
- _check("Ollama", "WARN", "not reachable at " + config.llm.api_base,
1387
- "brew services start ollama")
1388
- elif config.mode.value == "c":
1389
- # Mode C — check API key
1390
- if config.llm.api_key:
1391
- _check("API key", "PASS",
1392
- f"provider={config.llm.provider}, key=***{config.llm.api_key[-4:]}")
1393
- else:
1394
- _check("API key", "WARN", "no API key configured",
1395
- "slm provider set")
1396
- except Exception:
1397
- pass # Config load failed — already caught above
1532
+ _check(
1533
+ "API key", "WARN", "no API key configured",
1534
+ "slm provider set",
1535
+ )
1536
+ except Exception:
1537
+ pass # Config load failed — already caught above
1398
1538
 
1399
1539
  # 9. Disk space
1400
1540
  slm_home = Path.home() / ".superlocalmemory"
@@ -0,0 +1,192 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under AGPL-3.0-or-later - see LICENSE file
3
+ # Part of SuperLocalMemory v3.4.21 — LLD-05 §8
4
+
5
+ """CLI — ``slm context prestage`` and ``slm connect --disable`` (LLD-05).
6
+
7
+ Kept in a focused module (instead of bloating cli/commands.py past its cap).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json as _json
13
+ import os
14
+ from argparse import Namespace
15
+ from pathlib import Path
16
+ from typing import Callable
17
+
18
+ from superlocalmemory.hooks.antigravity_adapter import AntigravityAdapter
19
+ from superlocalmemory.hooks.copilot_adapter import CopilotAdapter
20
+ from superlocalmemory.hooks.cursor_adapter import CursorAdapter
21
+ from superlocalmemory.hooks.context_payload import (
22
+ RecallFn,
23
+ build_payload,
24
+ format_decisions,
25
+ format_entities,
26
+ format_memories,
27
+ format_topics,
28
+ )
29
+ from superlocalmemory.hooks.cross_platform_connector import (
30
+ CrossPlatformConnector,
31
+ )
32
+ from superlocalmemory.mcp.tools_context import prestage_context
33
+
34
+
35
+ def _noop_recall(_q: str, _limit: int, _profile: str) -> list[dict]:
36
+ """Placeholder recall when the daemon/engine isn't reachable."""
37
+ return []
38
+
39
+
40
+ def _get_recall_fn() -> RecallFn:
41
+ """Wire the real recall engine if available, else no-op."""
42
+ try:
43
+ from superlocalmemory.core.engine import Engine # pragma: no cover
44
+ eng = Engine.get_shared()
45
+ def _fn(q: str, limit: int, profile_id: str) -> list[dict]:
46
+ try:
47
+ return eng.recall(q, limit=limit) or [] # type: ignore
48
+ except Exception:
49
+ return []
50
+ return _fn
51
+ except Exception:
52
+ return _noop_recall
53
+
54
+
55
+ def _default_sync_log_db() -> Path:
56
+ return Path(os.environ.get("SLM_MEMORY_DB",
57
+ str(Path.home() / ".superlocalmemory"
58
+ / "memory.db")))
59
+
60
+
61
+ def build_default_adapters(
62
+ *, base_dir: Path | None = None,
63
+ recall_fn: RecallFn | None = None,
64
+ sync_log_db: Path | None = None,
65
+ ) -> list:
66
+ base = Path(base_dir or Path.cwd())
67
+ recall = recall_fn or _get_recall_fn()
68
+ db = sync_log_db or _default_sync_log_db()
69
+ adapters: list = []
70
+ adapters.append(CursorAdapter(scope="project", base_dir=base,
71
+ sync_log_db=db, recall_fn=recall))
72
+ adapters.append(CursorAdapter(scope="global", base_dir=Path.home(),
73
+ sync_log_db=db, recall_fn=recall))
74
+ adapters.append(AntigravityAdapter(scope="workspace", base_dir=base,
75
+ sync_log_db=db, recall_fn=recall))
76
+ adapters.append(AntigravityAdapter(scope="global", base_dir=Path.home(),
77
+ sync_log_db=db, recall_fn=recall))
78
+ adapters.append(CopilotAdapter(base_dir=base, sync_log_db=db,
79
+ recall_fn=recall))
80
+ return adapters
81
+
82
+
83
+ # ---------------------------------------------------------------------------
84
+ # `slm connect` (LLD-05 mode)
85
+ # ---------------------------------------------------------------------------
86
+
87
+
88
+ def cmd_connect_cross_platform(args: Namespace) -> None:
89
+ """LLD-05 mode: orchestrate cross-platform adapters."""
90
+ adapters = build_default_adapters()
91
+ connector = CrossPlatformConnector(adapters)
92
+
93
+ disable_target = getattr(args, "disable", None)
94
+ if disable_target:
95
+ ok = connector.disable(disable_target)
96
+ msg = {"adapter": disable_target, "disabled": ok}
97
+ if getattr(args, "json", False):
98
+ print(_json.dumps(msg))
99
+ else:
100
+ print(f"{'Disabled' if ok else 'Not found'}: {disable_target}")
101
+ return
102
+
103
+ if getattr(args, "dry_run", False):
104
+ statuses = connector.detect()
105
+ out = [
106
+ {"name": s.name, "active": s.active, "target": s.target_path}
107
+ for s in statuses
108
+ ]
109
+ if getattr(args, "json", False):
110
+ print(_json.dumps({"detected": out}))
111
+ else:
112
+ for row in out:
113
+ mark = "[+]" if row["active"] else "[-]"
114
+ print(f" {mark} {row['name']:25s} {row['target']}")
115
+ return
116
+
117
+ results = connector.connect()
118
+ if getattr(args, "json", False):
119
+ print(_json.dumps({"results": results}))
120
+ else:
121
+ for name, res in results.items():
122
+ print(f" {name}: {res}")
123
+
124
+
125
+ # ---------------------------------------------------------------------------
126
+ # `slm context prestage`
127
+ # ---------------------------------------------------------------------------
128
+
129
+
130
+ def _render_markdown(payload) -> str:
131
+ lines = [
132
+ "# --- SLM Context ---",
133
+ f"_Version {payload.version} — {payload.generated_at}_",
134
+ "",
135
+ "## Topics",
136
+ format_topics(payload),
137
+ "",
138
+ "## Entities",
139
+ format_entities(payload),
140
+ "",
141
+ "## Recent decisions",
142
+ format_decisions(payload),
143
+ "",
144
+ "## Project memories",
145
+ format_memories(payload),
146
+ ]
147
+ return "\n".join(lines)
148
+
149
+
150
+ def cmd_context(args: Namespace) -> None:
151
+ """Dispatcher for ``slm context <subcommand>``."""
152
+ sub = getattr(args, "subcommand", None) or "prestage"
153
+ if sub != "prestage":
154
+ print(f"Unknown subcommand: {sub}")
155
+ return
156
+
157
+ query = getattr(args, "query", "") or ""
158
+ limit = int(getattr(args, "limit", 5))
159
+ profile_id = getattr(args, "profile_id", "default")
160
+
161
+ if getattr(args, "tool", False):
162
+ # Direct MCP-like tool response.
163
+ result = prestage_context(
164
+ query, limit=limit, profile_id=profile_id,
165
+ recall_fn=_get_recall_fn(),
166
+ )
167
+ print(_json.dumps(result, indent=2))
168
+ return
169
+
170
+ # Build a full markdown / JSON payload for CLI consumption.
171
+ payload = build_payload(
172
+ profile_id, "project", Path.cwd(),
173
+ recall_fn=_get_recall_fn(),
174
+ )
175
+ if getattr(args, "json", False):
176
+ print(_json.dumps({
177
+ "query": query,
178
+ "topics": list(payload.topics),
179
+ "entities": list(payload.entities),
180
+ "decisions": list(payload.recent_decisions),
181
+ "memories": list(payload.project_memories),
182
+ "generated_at": payload.generated_at,
183
+ }))
184
+ return
185
+ print(_render_markdown(payload))
186
+
187
+
188
+ __all__ = (
189
+ "build_default_adapters",
190
+ "cmd_context",
191
+ "cmd_connect_cross_platform",
192
+ )
@@ -473,8 +473,22 @@ class DaemonHandler(BaseHTTPRequestHandler):
473
473
  query = params.get("q", [""])[0]
474
474
  limit = int(params.get("limit", ["20"])[0])
475
475
 
476
+ # S9-DASH-02: session_id for outcome-queue enqueue.
477
+ # Priority: ?session_id= query arg > X-SLM-Session-Id
478
+ # header > synthetic "cli:<ts>". Without any of these
479
+ # the recall still works — it just doesn't produce a
480
+ # pending_outcome (hook-based signals can't match).
481
+ session_id = params.get("session_id", [""])[0]
482
+ if not session_id:
483
+ session_id = self.headers.get("X-SLM-Session-Id", "")
484
+ if not session_id:
485
+ import time as _t
486
+ session_id = f"http:{int(_t.time() * 1000)}"
487
+
476
488
  engine = _get_engine()
477
- response = engine.recall(query, limit=limit)
489
+ response = engine.recall(
490
+ query, limit=limit, session_id=session_id,
491
+ )
478
492
  results = [
479
493
  {"content": r.fact.content, "score": round(r.score, 4),
480
494
  "fact_type": getattr(r.fact.fact_type, 'value', str(r.fact.fact_type)),
@@ -0,0 +1,80 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under AGPL-3.0-or-later - see LICENSE file
3
+ # Part of SuperLocalMemory v3.4.21 — LLD-06 §7.2
4
+
5
+ """CLI handler for ``slm db migrate``.
6
+
7
+ LLD reference: ``.backup/active-brain/lld/LLD-06-windows-binary-and-legacy-migration.md``
8
+ Section 7.2 (slm db migrate CLI).
9
+
10
+ Thin wrapper over the canonical runner in
11
+ ``superlocalmemory.storage.migration_runner``. This module owns only
12
+ the user-facing surface (stdout formatting + exit codes). All DDL +
13
+ runner logic lives in LLD-07 territory — per H15, no migration schema
14
+ is defined or duplicated here.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ from argparse import Namespace
19
+ from pathlib import Path
20
+
21
+
22
+ # Canonical paths — match LLD-01 / LLD-07 layout. Callers can override
23
+ # via the ``learning_db_path`` / ``memory_db_path`` attributes on args
24
+ # (tests rely on this to point at fixture DBs).
25
+ DEFAULT_HOME = Path.home() / ".superlocalmemory"
26
+
27
+
28
+ def _resolve_paths(args: Namespace) -> tuple[Path, Path]:
29
+ learning = getattr(args, "learning_db_path", None)
30
+ memory = getattr(args, "memory_db_path", None)
31
+ if learning is None:
32
+ learning = DEFAULT_HOME / "learning.db"
33
+ if memory is None:
34
+ memory = DEFAULT_HOME / "memory.db"
35
+ return Path(learning), Path(memory)
36
+
37
+
38
+ def cmd_db_migrate(args: Namespace) -> int:
39
+ """Apply pending migrations or report status.
40
+
41
+ Behaviour:
42
+ - ``--status`` prints the per-migration status recorded in each
43
+ DB's ``migration_log``. Exit 0 unless reading fails.
44
+ - ``--dry-run`` runs the runner in dry-run mode (no writes).
45
+ - Default: runs ``apply_all``.
46
+
47
+ Exit codes (also returned for tests that capture return value):
48
+ - 0 on success (no failed migrations).
49
+ - 1 if any migration is reported as ``failed``.
50
+ """
51
+ from superlocalmemory.storage.migration_runner import apply_all, status
52
+
53
+ learning_db, memory_db = _resolve_paths(args)
54
+
55
+ if getattr(args, "status", False):
56
+ report = status(learning_db, memory_db)
57
+ if not report:
58
+ print("(no migrations registered)")
59
+ else:
60
+ for name, state in report.items():
61
+ print(f" {name}: {state}")
62
+ return 0
63
+
64
+ dry_run = bool(getattr(args, "dry_run", False))
65
+ result = apply_all(learning_db, memory_db, dry_run=dry_run)
66
+ applied = result.get("applied", [])
67
+ skipped = result.get("skipped", [])
68
+ failed = result.get("failed", [])
69
+ print(
70
+ f"Applied={len(applied)} Skipped={len(skipped)} Failed={len(failed)}"
71
+ )
72
+ if failed:
73
+ details = result.get("details", {})
74
+ for name in failed:
75
+ print(f" FAILED {name}: {details.get(name, '(no detail)')}")
76
+ return 1
77
+ return 0
78
+
79
+
80
+ __all__ = ("cmd_db_migrate", "DEFAULT_HOME")