superlocalmemory 3.8.0 → 3.8.2

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 (134) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/README.md +32 -120
  3. package/package.json +9 -2
  4. package/plugin/.claude-plugin/plugin.json +1 -2
  5. package/plugin/CLAUDE.md +3 -3
  6. package/plugin/agents/slm-governance-advisor.md +1 -1
  7. package/plugin/agents/slm-loop-runner.md +1 -1
  8. package/plugin/agents/slm-memory-advisor.md +2 -2
  9. package/plugin/agents/slm-optimize-advisor.md +1 -1
  10. package/plugin/requirements.txt +1 -1
  11. package/plugin/skills/slm-cache/SKILL.md +1 -1
  12. package/plugin/skills/slm-compress/SKILL.md +1 -1
  13. package/plugin/skills/slm-governance/SKILL.md +1 -1
  14. package/plugin/skills/slm-graph/SKILL.md +1 -1
  15. package/plugin/skills/slm-loop/SKILL.md +1 -1
  16. package/plugin/skills/slm-mesh/SKILL.md +1 -1
  17. package/plugin/skills/slm-profile/SKILL.md +1 -1
  18. package/plugin/skills/slm-recall/SKILL.md +3 -5
  19. package/plugin/skills/slm-remember/SKILL.md +1 -1
  20. package/plugin/skills/slm-scope/SKILL.md +1 -1
  21. package/plugin/skills/slm-session/SKILL.md +1 -1
  22. package/plugin/skills/slm-status/SKILL.md +1 -1
  23. package/plugin-src/rules/AGENTS.md +2 -1
  24. package/plugin-src/skills/slm-cache/SKILL.md +1 -1
  25. package/plugin-src/skills/slm-compress/SKILL.md +1 -1
  26. package/plugin-src/skills/slm-graph/SKILL.md +1 -1
  27. package/plugin-src/skills/slm-recall/SKILL.md +3 -5
  28. package/plugin-src/skills/slm-remember/SKILL.md +1 -1
  29. package/plugin-src/skills/slm-session/SKILL.md +1 -1
  30. package/plugin-src/skills/slm-status/SKILL.md +1 -1
  31. package/pyproject.toml +2 -1
  32. package/scripts/postinstall.js +7 -1
  33. package/src/superlocalmemory/__init__.py +1 -1
  34. package/src/superlocalmemory/cli/commands.py +494 -9
  35. package/src/superlocalmemory/cli/daemon.py +7 -0
  36. package/src/superlocalmemory/cli/loop_cmd.py +2 -7
  37. package/src/superlocalmemory/cli/main.py +72 -7
  38. package/src/superlocalmemory/cli/setup_wizard.py +142 -16
  39. package/src/superlocalmemory/cli/version_banner.py +17 -3
  40. package/src/superlocalmemory/core/backend_orchestrator.py +18 -16
  41. package/src/superlocalmemory/core/component_healer.py +144 -0
  42. package/src/superlocalmemory/core/component_registry.py +487 -0
  43. package/src/superlocalmemory/core/config.py +21 -0
  44. package/src/superlocalmemory/core/embedding_worker.py +4 -5
  45. package/src/superlocalmemory/core/embeddings.py +132 -45
  46. package/src/superlocalmemory/core/engine.py +29 -22
  47. package/src/superlocalmemory/core/engine_ingestion.py +332 -45
  48. package/src/superlocalmemory/core/ingestion_command.py +154 -25
  49. package/src/superlocalmemory/core/injection.py +12 -7
  50. package/src/superlocalmemory/core/maintenance.py +43 -0
  51. package/src/superlocalmemory/core/maintenance_scheduler.py +44 -6
  52. package/src/superlocalmemory/core/recall_pipeline.py +42 -4
  53. package/src/superlocalmemory/core/store_pipeline.py +195 -20
  54. package/src/superlocalmemory/hooks/hook_handlers.py +6 -1
  55. package/src/superlocalmemory/hooks/portable_kit.py +34 -2
  56. package/src/superlocalmemory/learning/model_rollback.py +3 -0
  57. package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
  58. package/src/superlocalmemory/learning/reward.py +50 -0
  59. package/src/superlocalmemory/learning/source_quality.py +523 -1
  60. package/src/superlocalmemory/loops/ledger.py +25 -5
  61. package/src/superlocalmemory/mcp/_daemon_proxy.py +6 -2
  62. package/src/superlocalmemory/mcp/_pool_adapter.py +4 -1
  63. package/src/superlocalmemory/mcp/server.py +11 -30
  64. package/src/superlocalmemory/mcp/tools_active.py +1 -1
  65. package/src/superlocalmemory/mcp/tools_core.py +21 -5
  66. package/src/superlocalmemory/mcp/tools_learning.py +2 -2
  67. package/src/superlocalmemory/retrieval/bridge_discovery.py +14 -0
  68. package/src/superlocalmemory/retrieval/engine.py +53 -21
  69. package/src/superlocalmemory/retrieval/reranker.py +3 -4
  70. package/src/superlocalmemory/retrieval/spreading_activation.py +68 -38
  71. package/src/superlocalmemory/server/config_file.py +90 -0
  72. package/src/superlocalmemory/server/origin.py +50 -0
  73. package/src/superlocalmemory/server/routes/backup.py +293 -70
  74. package/src/superlocalmemory/server/routes/behavioral.py +342 -61
  75. package/src/superlocalmemory/server/routes/brain.py +57 -16
  76. package/src/superlocalmemory/server/routes/config_api.py +84 -82
  77. package/src/superlocalmemory/server/routes/entity.py +100 -23
  78. package/src/superlocalmemory/server/routes/evolution.py +103 -100
  79. package/src/superlocalmemory/server/routes/learning.py +286 -105
  80. package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
  81. package/src/superlocalmemory/server/routes/memories.py +8 -3
  82. package/src/superlocalmemory/server/routes/mesh.py +121 -32
  83. package/src/superlocalmemory/server/routes/ratelimit.py +33 -25
  84. package/src/superlocalmemory/server/routes/stats.py +93 -155
  85. package/src/superlocalmemory/server/routes/token.py +3 -13
  86. package/src/superlocalmemory/server/routes/v3_api.py +184 -20
  87. package/src/superlocalmemory/server/unified_daemon.py +732 -41
  88. package/src/superlocalmemory/storage/embedding_migrator.py +235 -0
  89. package/src/superlocalmemory/storage/migration_runner.py +79 -1
  90. package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -0
  91. package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +270 -0
  92. package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
  93. package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
  94. package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
  95. package/src/superlocalmemory/storage/schema.py +49 -1
  96. package/src/superlocalmemory/storage/schema_v32.py +2 -0
  97. package/src/superlocalmemory/storage/schema_v347.py +4 -0
  98. package/src/superlocalmemory/ui/index.html +6 -8
  99. package/src/superlocalmemory/ui/js/core.js +52 -9
  100. package/src/superlocalmemory/ui/js/dashboard.js +169 -82
  101. package/src/superlocalmemory/ui/js/od-backup.js +156 -65
  102. package/src/superlocalmemory/ui/js/od-brain.js +88 -51
  103. package/src/superlocalmemory/ui/js/od-components.js +147 -0
  104. package/src/superlocalmemory/ui/js/od-entities.js +65 -22
  105. package/src/superlocalmemory/ui/js/od-graph.js +46 -4
  106. package/src/superlocalmemory/ui/js/od-health.js +18 -0
  107. package/src/superlocalmemory/ui/js/od-memories.js +84 -5
  108. package/src/superlocalmemory/ui/js/od-mesh.js +23 -9
  109. package/src/superlocalmemory/ui/js/od-operations.js +36 -0
  110. package/src/superlocalmemory/ui/js/od-settings.js +186 -63
  111. package/src/superlocalmemory/ui/js/od-shell.js +249 -33
  112. package/src/superlocalmemory/ui/js/od-skills.js +44 -17
  113. package/src/superlocalmemory/ui/js/settings.js +15 -1
  114. package/plugin-src/.mcp.json +0 -12
  115. package/plugin-src/agents/slm-governance-advisor.md +0 -80
  116. package/plugin-src/agents/slm-loop-runner.md +0 -71
  117. package/plugin-src/agents/slm-memory-advisor.md +0 -49
  118. package/plugin-src/agents/slm-optimize-advisor.md +0 -44
  119. package/plugin-src/commands/slm-loop.md +0 -31
  120. package/plugin-src/hooks/.gitkeep +0 -0
  121. package/plugin-src/hooks/hooks.json +0 -102
  122. package/plugin-src/manifest.json +0 -30
  123. package/plugin-src/requirements.txt +0 -1
  124. package/plugin-src/rules/CLAUDE.md.fragment +0 -44
  125. package/plugin-src/scripts/ensure-venv.bat +0 -122
  126. package/plugin-src/scripts/ensure-venv.sh +0 -105
  127. package/plugin-src/scripts/slm-launch +0 -62
  128. package/plugin-src/scripts/slm-launch.bat +0 -23
  129. package/plugin-src/settings.json +0 -25
  130. package/plugin-src/skills/slm-governance/SKILL.md +0 -248
  131. package/plugin-src/skills/slm-loop/SKILL.md +0 -99
  132. package/plugin-src/skills/slm-mesh/SKILL.md +0 -282
  133. package/plugin-src/skills/slm-profile/SKILL.md +0 -148
  134. package/plugin-src/skills/slm-scope/SKILL.md +0 -176
@@ -267,6 +267,13 @@ def daemon_request(
267
267
  if capability is not None:
268
268
  headers["X-SLM-Daemon-Capability"] = capability
269
269
  headers["X-SLM-Target-Instance"] = descriptor.instance_id
270
+ # Daemon ownership proves that this CLI targets the local instance; it
271
+ # does not replace a dashboard user's profile-scoped authorization in
272
+ # governed workspaces. The user opts in by supplying an explicit
273
+ # session through the process environment (never logged or persisted).
274
+ user_session = os.environ.get("SLM_USER_SESSION", "").strip()
275
+ if user_session:
276
+ headers["X-SLM-User-Session"] = user_session
270
277
  req = urllib.request.Request(url, data=data, headers=headers, method=method)
271
278
  resp = urllib.request.urlopen(req, timeout=timeout_seconds)
272
279
  return json.loads(resp.read().decode())
@@ -19,11 +19,11 @@ store rooted at the active data root (``SLM_DATA_DIR`` or
19
19
  from __future__ import annotations
20
20
 
21
21
  import json
22
- import os
23
22
  from argparse import Namespace
24
23
  from pathlib import Path
25
24
  from typing import Any
26
25
 
26
+ from superlocalmemory.infra.data_root import canonical_data_root
27
27
  from superlocalmemory.loops import (
28
28
  Bounds,
29
29
  LapResult,
@@ -35,12 +35,7 @@ from superlocalmemory.loops import (
35
35
 
36
36
 
37
37
  def _data_root() -> Path:
38
- value = (
39
- os.environ.get("SLM_DATA_DIR")
40
- or os.environ.get("SL_MEMORY_PATH")
41
- or os.environ.get("SLM_HOME")
42
- )
43
- return Path(value).expanduser() if value else Path.home() / ".superlocalmemory"
38
+ return canonical_data_root()
44
39
 
45
40
 
46
41
  class _FailOpenLedger:
@@ -82,6 +82,8 @@ _NO_DAEMON_COMMANDS = {
82
82
  "optimize", "cache", "compress", "help-optimize",
83
83
  # Bounded loops use an in-process engine store, not the daemon.
84
84
  "loop",
85
+ # v3.8.2 super-help is pure text — never touch the daemon.
86
+ "help",
85
87
  # Lifecycle orchestration must run before any global auto-start hook.
86
88
  "serve", "restart",
87
89
  }
@@ -114,12 +116,17 @@ def main() -> None:
114
116
  or any(arg in {"-h", "--help"} for arg in sys.argv[1:])
115
117
  or (len(sys.argv) == 2 and sys.argv[1] in {"-v", "--version"})
116
118
  )
119
+ _is_connect_dry_run = (
120
+ len(sys.argv) >= 2
121
+ and sys.argv[1] == "connect"
122
+ and "--dry-run" in sys.argv[2:]
123
+ )
117
124
 
118
125
  # WP-07: lazy first-run init — runs after hook/mcp fast-paths so stdout
119
126
  # is never polluted on those paths (CRIT-3, MCP JSON-RPC purity).
120
127
  # Guarded: any failure must not crash the CLI (AC4).
121
128
  _is_mcp_cmd = len(sys.argv) >= 2 and sys.argv[1] == "mcp"
122
- if not _is_mcp_cmd and not _is_metadata_cmd:
129
+ if not _is_mcp_cmd and not _is_metadata_cmd and not _is_connect_dry_run:
123
130
  try:
124
131
  from superlocalmemory.cli._lazy_init import _ensure_initialized
125
132
  _ensure_initialized()
@@ -139,7 +146,7 @@ def main() -> None:
139
146
 
140
147
  # One-time post-upgrade banner — silent for fresh installs and
141
148
  # same-version runs. Guarded against I/O errors internally.
142
- if not _is_mcp_stdio and not _is_metadata_cmd:
149
+ if not _is_mcp_stdio and not _is_metadata_cmd and not _is_connect_dry_run:
143
150
  from superlocalmemory.cli.version_banner import check_and_emit_upgrade_banner
144
151
  if check_and_emit_upgrade_banner(_ver):
145
152
  # First post-upgrade invocation: apply the data-dir migration if
@@ -278,6 +285,37 @@ def main() -> None:
278
285
  db_scale_p.add_argument("--stage-id", help="Stage identifier required by verify/promote")
279
286
  db_scale_p.add_argument("--backup-id", help="Backup identifier required by rollback")
280
287
 
288
+ db_reembed_p = db_sub.add_parser(
289
+ "reembed",
290
+ help="Backfill NULL embeddings (facts never embedded)",
291
+ )
292
+ db_reembed_p.add_argument(
293
+ "--missing-only",
294
+ action="store_true",
295
+ default=True,
296
+ dest="missing_only",
297
+ help="Only embed facts with NULL embedding (default and only mode)",
298
+ )
299
+ db_reembed_p.add_argument(
300
+ "--all-profiles",
301
+ action="store_true",
302
+ default=False,
303
+ dest="all_profiles",
304
+ help="Process all profiles (default: active profile only)",
305
+ )
306
+ db_reembed_p.add_argument(
307
+ "--limit",
308
+ type=int,
309
+ default=None,
310
+ metavar="N",
311
+ help="Maximum facts to embed per run (default: no limit)",
312
+ )
313
+ db_reembed_p.add_argument(
314
+ "--json",
315
+ action="store_true",
316
+ help="Output structured JSON",
317
+ )
318
+
281
319
  # -- Mesh inspection (v3.7.9, M-03) --------------------------------
282
320
  mesh_p = sub.add_parser("mesh", help="Inspect the local agent mesh (status/peers)")
283
321
  mesh_p.add_argument(
@@ -322,9 +360,11 @@ def main() -> None:
322
360
  )
323
361
  recall_p.add_argument(
324
362
  "--fast", action="store_true",
325
- help="Skip graph-assisted candidate expansion and remote agentic verification for a "
326
- "latency-bounded response. Other configured retrieval channels still run. "
327
- "Use when you need recall before a tool call (e.g. before WebSearch).",
363
+ help="Force-skip the internal agentic verification round (all six retrieval "
364
+ "channels + reranker still run). This is already the default (client-driven "
365
+ "agentic): SLM returns fast local results + confidence signals and lets the "
366
+ "calling LLM re-query on low confidence. Pass --fast to guarantee the fast "
367
+ "path even where the server is configured for internal agentic.",
328
368
  )
329
369
  # v3.6.15: shared memory is opt-in. Unset (None) → resolve the configured
330
370
  # default (recall_include_global/shared, both False by default). Explicit
@@ -346,6 +386,15 @@ def main() -> None:
346
386
  "--no-shared", dest="include_shared", action="store_false", default=None,
347
387
  help="Exclude shared-scope facts from retrieval",
348
388
  )
389
+ # v3.8.2 client-driven agentic: agents parsing --json should ACT on the
390
+ # confidence signals rather than expect a server-side LLM to reformulate.
391
+ recall_p.epilog = (
392
+ "AGENT GUIDANCE (--json): recall returns fast local results plus "
393
+ "confidence signals. If 'no_confident_match' is true or "
394
+ "'answer_confidence' is low, rewrite the query into 1-3 more specific "
395
+ "sub-queries and call recall again before concluding nothing was found. "
396
+ "Confident match -> use it directly."
397
+ )
349
398
 
350
399
  forget_p = sub.add_parser("forget", help="Delete memories matching a query (fuzzy)")
351
400
  forget_p.add_argument("query", nargs="?", default=None, help="Query to match for deletion. Optional with --dry-run (previews all memories).")
@@ -392,6 +441,21 @@ def main() -> None:
392
441
  "--quick", action="store_true",
393
442
  help="Run only the fast checks (deps + config); skip daemon/embedding probes",
394
443
  )
444
+ doctor_p.add_argument(
445
+ "--fix", action="store_true",
446
+ help="Auto-repair fixable components (re-download missing models, "
447
+ "install sqlite-vec) before checking, then report",
448
+ )
449
+
450
+ # v3.8.2 super-help — grouped overview of every command + focused topics.
451
+ help_p = sub.add_parser(
452
+ "help",
453
+ help="Grouped overview of all commands + topics (modes, config, self-heal)",
454
+ )
455
+ help_p.add_argument(
456
+ "topic", nargs="?",
457
+ help="Optional topic: modes | config | self-heal",
458
+ )
395
459
 
396
460
  # LLD-06 §6.6 — `slm wrap <agent> [args...]` activates the Optimize
397
461
  # proxy for a specific agent. Supported agents: claude, claude-settings,
@@ -816,8 +880,9 @@ def main() -> None:
816
880
  sys.exit(0)
817
881
 
818
882
  # V3.3.19: Auto-trigger setup wizard on first use
819
- from superlocalmemory.cli.setup_wizard import check_first_use
820
- check_first_use(args.command)
883
+ if not (args.command == "connect" and getattr(args, "dry_run", False)):
884
+ from superlocalmemory.cli.setup_wizard import check_first_use
885
+ check_first_use(args.command)
821
886
 
822
887
  # V3.4.4: Auto-start daemon for all commands that need it.
823
888
  # SLM is always-on — close laptop, reboot, crash: daemon auto-recovers.
@@ -100,6 +100,25 @@ def _get_ram_gb() -> float:
100
100
  return 0.0
101
101
 
102
102
 
103
+ def _ollama_available() -> bool:
104
+ """True if a local Ollama server is reachable (or its binary is installed).
105
+
106
+ v3.8.2: used to RECOMMEND Mode B at setup with a single keypress. Bounded
107
+ and fail-safe — a slow/absent Ollama never blocks or slows the wizard.
108
+ """
109
+ try:
110
+ import httpx
111
+
112
+ if httpx.get("http://localhost:11434/api/tags", timeout=1.5).status_code == 200:
113
+ return True
114
+ except Exception:
115
+ pass
116
+ try:
117
+ return shutil.which("ollama") is not None
118
+ except Exception:
119
+ return False
120
+
121
+
103
122
  # ---------------------------------------------------------------------------
104
123
  # Model download
105
124
  # ---------------------------------------------------------------------------
@@ -388,14 +407,23 @@ def run_wizard(auto: bool = False) -> None:
388
407
  return
389
408
 
390
409
  # -- Step 2: Mode selection --
410
+ # v3.8.2: pre-detect Ollama so we can recommend Mode B with one keypress.
411
+ # No detection → zero-config Mode A. (Non-interactive always stays A: Mode B
412
+ # needs a pulled model, so we never assume it without the user confirming.)
413
+ ollama_present = _ollama_available()
414
+ default_mode = "b" if ollama_present else "a"
391
415
  print()
392
416
  print("─── Step 2/10: Choose Operating Mode ───")
393
417
  print()
394
- print(" [A] Local Guardian (recommended)")
418
+ if ollama_present:
419
+ print(" ✓ Ollama detected on this machine — Mode B recommended.")
420
+ print(" Press Enter to accept, or pick another mode.")
421
+ print()
422
+ print(" [A] Local Guardian" + ("" if ollama_present else " (recommended)"))
395
423
  print(" No model-provider call in the core memory path.")
396
424
  print(" Review optional integrations and network policy for your deployment.")
397
425
  print()
398
- print(" [B] Smart Local")
426
+ print(" [B] Smart Local" + (" (recommended — Ollama detected)" if ollama_present else ""))
399
427
  print(" Local LLM via Ollama for enrichment.")
400
428
  print(" Data stays on your machine.")
401
429
  print()
@@ -405,10 +433,13 @@ def run_wizard(auto: bool = False) -> None:
405
433
  print()
406
434
 
407
435
  if interactive:
408
- choice = _prompt(" Select mode [A/B/C] (default: A): ", "a").lower()
436
+ choice = _prompt(
437
+ f" Select mode [A/B/C] (default: {default_mode.upper()}): ",
438
+ default_mode,
439
+ ).lower()
409
440
  else:
410
441
  choice = "a"
411
- print(" Auto-selecting Mode A (non-interactive)")
442
+ print(" Auto-selecting Mode A (non-interactive, zero-config)")
412
443
 
413
444
  if choice not in ("a", "b", "c"):
414
445
  print(f" Invalid choice '{choice}', using Mode A.")
@@ -496,6 +527,15 @@ def run_wizard(auto: bool = False) -> None:
496
527
  else:
497
528
  print(f"\n ✓ CodeGraph disabled (enable later in {cg_config_path})")
498
529
 
530
+ # v3.8.2: up-front download summary so a large pull is never a surprise on
531
+ # a metered/slow link. Sizes approximate, one-time, cached locally.
532
+ if st_ok and not _embedding_is_remote(config):
533
+ print()
534
+ print(" Models to download (one-time, cached locally):")
535
+ print(" • Embedding model ~500 MB — required for semantic recall")
536
+ print(" • Reranker model ~130 MB — result quality")
537
+ print(" • Compression model ~560 MB — optional (you'll be asked)")
538
+
499
539
  # -- Step 4: Download models --
500
540
  print()
501
541
  # H-06 (CVE-2025-14926): a malicious HuggingFace checkpoint can execute code
@@ -532,8 +572,25 @@ def run_wizard(auto: bool = False) -> None:
532
572
  _download_reranker(_RERANKER_MODEL)
533
573
 
534
574
  print()
535
- print("─── Step 4c/10: Download Compression Model (LLMLingua-2) ───")
536
- _download_compressor(_COMPRESSOR_MODEL)
575
+ print("─── Step 4c/10: Compression Model (LLMLingua-2, ~560MB) ───")
576
+ print(" Only used for aggressive prompt compression. It also downloads")
577
+ print(" automatically the first time you enable that feature — so skipping")
578
+ print(" here is safe.")
579
+ # v3.8.2: consent-gate the 560MB compressor (was unconditional). Skip by
580
+ # default; it lazy-downloads on first real use of compression.
581
+ if not st_ok:
582
+ print(" ⚠ Skipped (sentence-transformers not installed)")
583
+ elif interactive:
584
+ comp_choice = _prompt(
585
+ " Download the ~560MB compression model now? [y/N] (default: N): ",
586
+ "n",
587
+ ).lower()
588
+ if comp_choice in ("y", "yes"):
589
+ _download_compressor(_COMPRESSOR_MODEL)
590
+ else:
591
+ print(" ✓ Skipped — downloads automatically when you enable compression.")
592
+ else:
593
+ print(" ✓ Skipped (non-interactive) — downloads on first use.")
537
594
 
538
595
  # -- Step 5: Daemon Configuration (v3.4.3) --
539
596
  print()
@@ -655,17 +712,21 @@ def run_wizard(auto: bool = False) -> None:
655
712
  print(" It detects degradation, generates improvements, and verifies them blindly.")
656
713
  print(" Requires an LLM backend (Claude CLI, Ollama, or API key).")
657
714
  print()
715
+ print(" OFF by default — when enabled it makes background LLM calls.")
658
716
  print(" [Y] Enable Skill Evolution")
659
- print(" [N] Disable (can enable later: slm config set evolution.enabled true)")
717
+ print(" [N] Keep disabled (enable later: slm config set evolution.enabled true)")
660
718
  print()
661
719
 
720
+ # v3.8.2: default OFF (was Y) — consistent with the npm profiles and no
721
+ # surprise outbound LLM calls for a fresh user. Opt-in, not opt-out.
662
722
  if interactive:
663
- evo_choice = _prompt(" Enable Skill Evolution? [Y/n] (default: Y): ", "y").lower()
723
+ evo_choice = _prompt(" Enable Skill Evolution? [y/N] (default: N): ", "n").lower()
664
724
  else:
665
- evo_choice = "y"
666
- print(" Auto-enabling Skill Evolution (non-interactive)")
725
+ evo_choice = "n"
726
+ print(" Skill Evolution stays OFF (non-interactive) — enable later with:")
727
+ print(" slm config set evolution.enabled true")
667
728
 
668
- evolution_enabled = evo_choice in ("", "y", "yes")
729
+ evolution_enabled = evo_choice in ("y", "yes")
669
730
 
670
731
  # Write evolution config to config.json directly
671
732
  # (SLMConfig.save() doesn't serialize evolution)
@@ -753,6 +814,21 @@ def run_wizard(auto: bool = False) -> None:
753
814
  else:
754
815
  print(" Adapters: none (enable via: slm adapters enable gmail)")
755
816
  print()
817
+ # v3.8.2: surface component self-heal status so the user leaves setup
818
+ # KNOWING everything they need is present — or exactly what to run if not.
819
+ try:
820
+ from superlocalmemory.core import component_registry as _cr
821
+
822
+ _sum = _cr.snapshot(config)["summary"]
823
+ if _sum.get("healthy"):
824
+ print(" ✓ All components ready — models and dependencies present.")
825
+ else:
826
+ print(f" ⚠ {_sum.get('missing', 0)} component(s) need attention.")
827
+ print(" SLM auto-repairs fixable ones on daemon start. To check/fix now:")
828
+ print(" slm doctor (auto-fix: slm doctor --fix)")
829
+ except Exception:
830
+ pass
831
+ print()
756
832
  print(" Quick start:")
757
833
  print(' slm remember "your first memory"')
758
834
  print(' slm recall "search query"')
@@ -760,8 +836,8 @@ def run_wizard(auto: bool = False) -> None:
760
836
  print(" slm adapters enable gmail → start Gmail ingestion")
761
837
  print()
762
838
  print(" Need help?")
763
- print(" slm doctor diagnose issues")
764
- print(" slm --helpall commands")
839
+ print(" slm help every command, grouped (try: slm help self-heal)")
840
+ print(" slm doctordiagnose issues (auto-fix: slm doctor --fix)")
765
841
  print(" slm serve install — install auto-start service")
766
842
  print(" https://github.com/qualixar/superlocalmemory")
767
843
  print()
@@ -816,20 +892,70 @@ def check_first_use(command: str) -> None:
816
892
 
817
893
 
818
894
  def _configure_external_integrations(*, interactive: bool) -> bool:
819
- """Request consent before editing Claude Code configuration."""
895
+ """Request consent before editing Claude Code configuration.
896
+
897
+ v3.8.2: after the Claude Code step, a pip user can also wire every OTHER
898
+ detected IDE in one step (previously only Claude Code was offered here;
899
+ other IDEs required a manual `slm connect <ide>` each).
900
+ """
820
901
  print()
821
902
  print(" Optional Claude Code integration can install the SLM plugin and hooks.")
822
903
  if not interactive:
823
904
  print(" Skipped in non-interactive setup. Run: slm connect claude-code")
824
905
  return False
906
+ changed = False
825
907
  choice = _prompt(
826
908
  " Install Claude Code plugin and hooks now? [y/N] (default: N): ",
827
909
  "n",
828
910
  ).lower()
829
- if choice not in ("y", "yes"):
911
+ if choice in ("y", "yes"):
912
+ changed = _install_external_integrations()
913
+ else:
830
914
  print(" Skipped. Run `slm connect claude-code` when ready.")
915
+ # v3.8.2: offer to connect all other detected IDEs (Cursor, VS Code, …).
916
+ changed = _configure_other_ides(interactive=interactive) or changed
917
+ return changed
918
+
919
+
920
+ def _configure_other_ides(*, interactive: bool) -> bool:
921
+ """Consent-gated: connect all OTHER detected IDEs (Cursor, VS Code, …).
922
+
923
+ Uses the same IDEConnector the dashboard uses. Best-effort — a failure to
924
+ detect or connect any single IDE never blocks setup.
925
+ """
926
+ if not interactive:
927
+ return False
928
+ try:
929
+ from superlocalmemory.hooks.ide_connector import IDEConnector
930
+
931
+ connector = IDEConnector()
932
+ installed = [s for s in connector.get_status() if s.get("installed")]
933
+ except Exception:
831
934
  return False
832
- return _install_external_integrations()
935
+ others = [s for s in installed if s.get("id") not in ("claude-code", "claude")]
936
+ if not others:
937
+ return False
938
+ names = ", ".join(s.get("name", s.get("id")) for s in others)
939
+ print()
940
+ print(f" Detected other IDEs: {names}")
941
+ print(" SLM can wire them all so memory works across every editor.")
942
+ choice = _prompt(" Connect them now? [Y/n] (default: Y): ", "y").lower()
943
+ if choice not in ("", "y", "yes"):
944
+ print(" Skipped. Connect any later with: slm connect <ide>")
945
+ return False
946
+ changed = False
947
+ for s in others:
948
+ ide_id = s.get("id")
949
+ label = s.get("name", ide_id)
950
+ try:
951
+ if connector.connect(ide_id):
952
+ print(f" ✓ {label}")
953
+ changed = True
954
+ else:
955
+ print(f" ⚠ {label} — connect later: slm connect {ide_id}")
956
+ except Exception:
957
+ print(f" ⚠ {label} — connect later: slm connect {ide_id}")
958
+ return changed
833
959
 
834
960
 
835
961
  def _install_external_integrations() -> bool:
@@ -26,6 +26,15 @@ _VERSION_PATTERN = re.compile(r"^[0-9A-Za-z.\-+_]{1,32}$")
26
26
 
27
27
  _MAX_MARKER_BYTES = 64 # a semver string is ≤ 32 chars; 64 is plenty
28
28
 
29
+ _RELEASE_NOTES: dict[str, tuple[str, ...]] = {
30
+ "3.8.1": (
31
+ "Large existing databases no longer delay daemon readiness",
32
+ "Failed enrichment retries stop after ten automatic attempts",
33
+ "Dashboard panes stay mounted between navigation changes",
34
+ "Local model workers stay warm for a 30-minute working session",
35
+ ),
36
+ }
37
+
29
38
 
30
39
  def _data_dir() -> Path:
31
40
  from superlocalmemory.infra.data_root import canonical_data_root
@@ -116,11 +125,16 @@ def _banner(prior: str | None, current: str) -> str:
116
125
  header = (f"SuperLocalMemory upgraded from {prior} to {current}"
117
126
  if prior else
118
127
  f"SuperLocalMemory upgraded to {current} (from an earlier version)")
128
+ notes = _RELEASE_NOTES.get(
129
+ current,
130
+ (
131
+ "The installed version has changed; existing memory remains in place",
132
+ "See the release notes for version-specific changes",
133
+ ),
134
+ )
119
135
  return "\n".join([
120
136
  header,
121
- " - Multi-IDE MCP processes now share a worker — large RAM drop",
122
- " - Feedback and learning signals flow from every IDE to the daemon",
123
- " - Silent data migration complete; no manual steps required",
137
+ *(f" - {note}" for note in notes),
124
138
  "Run `slm doctor` to verify your setup.",
125
139
  "",
126
140
  ])
@@ -52,7 +52,7 @@ class BackendOrchestrator:
52
52
  """Central coordinator for multi-backend architecture.
53
53
 
54
54
  Lifecycle:
55
- on_daemon_start() → migrate backends → ready
55
+ on_daemon_start() → initialize bounded backend state → ready
56
56
  sync_new_fact() → called from store_pipeline after SQLite write
57
57
  health_check() → returns status of all backends
58
58
  """
@@ -71,7 +71,7 @@ class BackendOrchestrator:
71
71
  # ------------------------------------------------------------------
72
72
 
73
73
  def on_daemon_start(self) -> None:
74
- """Called once on daemon startup. Order matters (F-11: rebalance before migration)."""
74
+ """Initialize bounded backend state without delaying daemon readiness."""
75
75
  logger.info("BackendOrchestrator: daemon starting")
76
76
 
77
77
  # 1. Apply schema (if not already applied)
@@ -82,18 +82,13 @@ class BackendOrchestrator:
82
82
  try:
83
83
  from superlocalmemory.core.tier_manager import evaluate_tiers
84
84
  self._tiers = evaluate_tiers
85
- logger.info("BackendOrchestrator: TierManager initialized")
85
+ logger.info("BackendOrchestrator: tier evaluator registered")
86
86
  except Exception as exc:
87
87
  logger.warning("TierManager init failed (non-fatal): %s", exc)
88
88
 
89
- # 3. Run initial tier rebalance FIRST (F-11: before migration)
90
- try:
91
- from superlocalmemory.core.tier_manager import evaluate_tiers as rebalance
92
- result = rebalance(self._db)
93
- logger.info("BackendOrchestrator: initial rebalance — %s",
94
- result.get("total_evaluated", "?"))
95
- except Exception as exc:
96
- logger.warning("Initial rebalance failed (non-fatal): %s", exc)
89
+ # Full-database tier evaluation belongs to MaintenanceScheduler. Running
90
+ # it here blocks FastAPI lifespan readiness on mature upgrade databases
91
+ # and occurs before optional projection backends are fully registered.
97
92
 
98
93
  self._recover_interrupted_scale_promotion()
99
94
 
@@ -107,12 +102,12 @@ class BackendOrchestrator:
107
102
  )
108
103
  return
109
104
 
110
- # 4. Initialize CozoDB if available
105
+ # 3. Initialize CozoDB if available
111
106
  cozo_available = self._detect_cozo()
112
107
  if cozo_available:
113
108
  self._init_cozo()
114
109
 
115
- # 5. Initialize LanceDB if available
110
+ # 4. Initialize LanceDB if available
116
111
  lancedb_available = self._detect_lancedb()
117
112
  if lancedb_available:
118
113
  self._init_lancedb()
@@ -139,13 +134,19 @@ class BackendOrchestrator:
139
134
  try:
140
135
  from superlocalmemory.core.scale_engine import ScaleEngineManager
141
136
 
142
- result = ScaleEngineManager(self._config, profile_id="default").recover_interrupted_promotion()
137
+ result = ScaleEngineManager(
138
+ self._config,
139
+ profile_id="default",
140
+ ).recover_interrupted_promotion()
143
141
  if result:
144
142
  logger.warning("Scale Engine promotion recovery: %s", result)
145
143
  except Exception as exc:
146
144
  # A scale projection is derived data. Startup must keep serving
147
145
  # canonical SQLite even if optional recovery itself is unhealthy.
148
- logger.error("Scale Engine recovery requires repair; Local Core remains active: %s", exc)
146
+ logger.error(
147
+ "Scale Engine recovery requires repair; Local Core remains active: %s",
148
+ exc,
149
+ )
149
150
 
150
151
  # ------------------------------------------------------------------
151
152
  # Incremental Sync (F-04: called from store_pipeline)
@@ -401,7 +402,8 @@ class BackendOrchestrator:
401
402
  def _apply_schema_v345(self) -> None:
402
403
  try:
403
404
  from superlocalmemory.storage.schema_v345 import (
404
- apply_migration, schema_version_applied,
405
+ apply_migration,
406
+ schema_version_applied,
405
407
  )
406
408
  # #47 fix: use raw_connection() — DatabaseManager has no `.conn`,
407
409
  # so the old code raised AttributeError that was silently swallowed,