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
@@ -163,4 +163,4 @@ multi-profile setup. To switch the active profile, see `slm-profile`.
163
163
 
164
164
  ---
165
165
 
166
- SuperLocalMemory v3.8.0 · Qualixar · AGPL-3.0-or-later
166
+ SuperLocalMemory v3.8.2 · Qualixar · AGPL-3.0-or-later
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "superlocalmemory"
3
- version = "3.8.0"
3
+ version = "3.8.2"
4
4
  description = "Local-first agent memory with auditable hybrid retrieval"
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-or-later"
@@ -171,6 +171,7 @@ superlocalmemory = ["ui/**/*", "optimize/NOTICE"]
171
171
  "share/superlocalmemory/codex/skills/slm-remember" = ["plugin-src/skills/slm-remember/SKILL.md"]
172
172
  "share/superlocalmemory/codex/skills/slm-session" = ["plugin-src/skills/slm-session/SKILL.md"]
173
173
  "share/superlocalmemory/codex/skills/slm-status" = ["plugin-src/skills/slm-status/SKILL.md"]
174
+ "share/superlocalmemory/portable-kit/rules" = ["plugin-src/rules/AGENTS.md"]
174
175
 
175
176
  [tool.pytest.ini_options]
176
177
  testpaths = ["tests"]
@@ -200,7 +200,13 @@ function main(argv = process.argv.slice(2)) {
200
200
 
201
201
  console.log(`SuperLocalMemory ${packageVersion}: isolated runtime verified.`);
202
202
  console.log('No memory database, IDE hooks, daemon, configuration, or models were changed.');
203
- console.log('Run `slm setup` explicitly when you are ready to configure SLM.');
203
+ console.log('');
204
+ console.log(' Next step — run the guided setup (picks your mode, downloads');
205
+ console.log(' models, connects your IDEs). Takes about a minute:');
206
+ console.log('');
207
+ console.log(' slm setup');
208
+ console.log('');
209
+ console.log(' Prefer to tune performance profiles? Use: slm reconfigure');
204
210
  console.log('');
205
211
  return 0;
206
212
  }
@@ -32,7 +32,7 @@ if "OMP_NUM_THREADS" not in os.environ:
32
32
  os.environ["OMP_NUM_THREADS"] = "2"
33
33
  # ---------------------------------------------------------------------------
34
34
 
35
- __version__ = "3.8.0"
35
+ __version__ = "3.8.2"
36
36
 
37
37
  _REQUIRED_VERSIONS = {
38
38
  "sentence_transformers": "5.3.0",
@@ -36,10 +36,138 @@ def _cmd_db_dispatch(args: Namespace) -> None:
36
36
  if rc:
37
37
  sys.exit(rc)
38
38
  return
39
- print("Usage: slm db migrate [--status] [--dry-run] | slm db scale <action>")
39
+ if sub == "reembed":
40
+ _cmd_db_reembed(args)
41
+ return
42
+ print(
43
+ "Usage: slm db migrate [--status] [--dry-run] "
44
+ "| slm db scale <action> "
45
+ "| slm db reembed [--missing-only] [--all-profiles] [--limit N]"
46
+ )
40
47
  sys.exit(2)
41
48
 
42
49
 
50
+ def _cmd_db_reembed(args: Namespace) -> None:
51
+ """Backfill NULL embeddings on atomic_facts.
52
+
53
+ Scans for atomic_facts rows with no embedding (facts stored while the
54
+ embedder was unavailable) and embeds them now. Safe to re-run — only
55
+ NULL rows are processed.
56
+
57
+ Usage:
58
+ slm db reembed --missing-only # backfill NULLs (default)
59
+ slm db reembed --missing-only --limit 500 # cap per run
60
+ slm db reembed --missing-only --all-profiles # cover every profile
61
+ """
62
+ import time
63
+
64
+ from superlocalmemory.core.config import SLMConfig
65
+ from superlocalmemory.core.engine import MemoryEngine
66
+ from superlocalmemory.storage.embedding_migrator import backfill_missing_embeddings
67
+
68
+ use_json = getattr(args, "json", False)
69
+ missing_only = getattr(args, "missing_only", True)
70
+ all_profiles = getattr(args, "all_profiles", False)
71
+ limit = getattr(args, "limit", None)
72
+
73
+ if not missing_only:
74
+ if use_json:
75
+ from superlocalmemory.cli.json_output import json_print
76
+ json_print(
77
+ "db-reembed",
78
+ error={
79
+ "code": "NOT_IMPLEMENTED",
80
+ "message": "Only --missing-only is supported. "
81
+ "For full re-embed use 'slm db migrate'.",
82
+ },
83
+ )
84
+ else:
85
+ print("Only --missing-only mode is supported. "
86
+ "For full re-embed use 'slm db migrate'.")
87
+ sys.exit(2)
88
+
89
+ try:
90
+ config = SLMConfig.load()
91
+ engine = MemoryEngine(config)
92
+ engine.initialize()
93
+ except Exception as exc:
94
+ if use_json:
95
+ from superlocalmemory.cli.json_output import json_print
96
+ json_print(
97
+ "db-reembed",
98
+ error={"code": "ENGINE_INIT_ERROR", "message": str(exc)},
99
+ )
100
+ else:
101
+ print(f"Error initializing engine: {exc}", file=sys.stderr)
102
+ sys.exit(1)
103
+
104
+ embedder = getattr(engine, "_embedder", None)
105
+ if embedder is None:
106
+ if use_json:
107
+ from superlocalmemory.cli.json_output import json_print
108
+ json_print(
109
+ "db-reembed",
110
+ error={
111
+ "code": "NO_EMBEDDER",
112
+ "message": "Embedder is not available. "
113
+ "Start the daemon first or check embedding configuration.",
114
+ },
115
+ )
116
+ else:
117
+ print(
118
+ "Embedder is not available. "
119
+ "Start the daemon ('slm serve') or check embedding config.",
120
+ file=sys.stderr,
121
+ )
122
+ engine.close()
123
+ sys.exit(1)
124
+
125
+ t0 = time.monotonic()
126
+ try:
127
+ result = backfill_missing_embeddings(
128
+ config,
129
+ engine._db,
130
+ embedder,
131
+ batch_size=50,
132
+ limit=limit,
133
+ all_profiles=all_profiles,
134
+ )
135
+ except Exception as exc:
136
+ if use_json:
137
+ from superlocalmemory.cli.json_output import json_print
138
+ json_print(
139
+ "db-reembed",
140
+ error={"code": "BACKFILL_ERROR", "message": str(exc)},
141
+ )
142
+ else:
143
+ print(f"Backfill error: {exc}", file=sys.stderr)
144
+ engine.close()
145
+ sys.exit(1)
146
+
147
+ elapsed = time.monotonic() - t0
148
+ engine.close()
149
+
150
+ if use_json:
151
+ from superlocalmemory.cli.json_output import json_print
152
+ json_print(
153
+ "db-reembed",
154
+ data={
155
+ "scanned": result["scanned"],
156
+ "embedded": result["embedded"],
157
+ "remaining_null": result["remaining_null"],
158
+ "elapsed_s": round(elapsed, 2),
159
+ },
160
+ )
161
+ else:
162
+ print(
163
+ f"Backfill complete — "
164
+ f"scanned={result['scanned']} "
165
+ f"embedded={result['embedded']} "
166
+ f"remaining_null={result['remaining_null']} "
167
+ f"elapsed={elapsed:.1f}s"
168
+ )
169
+
170
+
43
171
  def _cmd_mesh_dispatch(args: Namespace) -> None:
44
172
  """Route ``slm mesh ...`` inspection subcommands (M-03)."""
45
173
  from superlocalmemory.cli.mesh_cmd import cmd_mesh
@@ -159,7 +287,10 @@ def cmd_session(args: Namespace) -> None:
159
287
  def dispatch(args: Namespace) -> None:
160
288
  """Route CLI command to the appropriate handler."""
161
289
  # Auto-install/upgrade hooks on version change (single file read, ~0.1ms)
162
- if args.command not in ("hooks", "codex", "init", "mcp"):
290
+ if (
291
+ args.command not in ("hooks", "codex", "init", "mcp")
292
+ and not getattr(args, "dry_run", False)
293
+ ):
163
294
  try:
164
295
  from superlocalmemory.hooks.claude_code_hooks import auto_install_if_needed
165
296
  auto_install_if_needed()
@@ -235,6 +366,8 @@ def dispatch(args: Namespace) -> None:
235
366
  "help-optimize": _cmd_help_optimize,
236
367
  # V3.8.0 bounded loops (gate-verified agent loops, SLM-backed ledger)
237
368
  "loop": _cmd_loop,
369
+ # V3.8.2 super-help — grouped overview of every command + topics
370
+ "help": cmd_help,
238
371
  }
239
372
  handler = handlers.get(args.command)
240
373
  if handler:
@@ -958,16 +1091,22 @@ def _agents_md_source_factory():
958
1091
  Gracefully skips if absent — never fails the MCP write.
959
1092
  """
960
1093
  from pathlib import Path
1094
+ import sysconfig
961
1095
 
962
1096
  # Resolve relative to the package root (src/superlocalmemory/../../)
963
1097
  _pkg_root = Path(__file__).resolve().parents[3]
964
- _agents_src = _pkg_root / "plugin-src" / "rules" / "AGENTS.md"
1098
+ _candidates = (
1099
+ _pkg_root / "plugin-src" / "rules" / "AGENTS.md",
1100
+ Path(sysconfig.get_path("data")) / "share" / "superlocalmemory"
1101
+ / "portable-kit" / "rules" / "AGENTS.md",
1102
+ )
965
1103
 
966
1104
  def _read() -> str | None:
967
- if _agents_src.exists():
968
- return _agents_src.read_text(encoding="utf-8")
1105
+ for candidate in _candidates:
1106
+ if candidate.exists():
1107
+ return candidate.read_text(encoding="utf-8")
969
1108
  logger.warning(
970
- "WP-05 AGENTS.md not found at %s — skipping AGENTS.md write", _agents_src
1109
+ "WP-05 AGENTS.md is not bundled — skipping AGENTS.md write"
971
1110
  )
972
1111
  return None
973
1112
 
@@ -1008,9 +1147,10 @@ def cmd_connect(args: Namespace) -> None:
1008
1147
  here=here,
1009
1148
  profile=profile,
1010
1149
  agents_md_source=_agents_md_source_factory(),
1150
+ dry_run=getattr(args, "dry_run", False),
1011
1151
  )
1012
1152
 
1013
- if not result.get("error"):
1153
+ if not result.get("error") and not getattr(args, "dry_run", False):
1014
1154
  from superlocalmemory.infra.local_diagnostics import record_operation
1015
1155
 
1016
1156
  record_operation("activation", client=ide_arg)
@@ -1029,6 +1169,7 @@ def cmd_connect(args: Namespace) -> None:
1029
1169
  sys.exit(1)
1030
1170
 
1031
1171
  status_sym = {"wrote": "[+]", "merged": "[~]", "unchanged": "[=]",
1172
+ "would_write": "[~]",
1032
1173
  "skipped": "[s]", "error": "[!]"}.get(
1033
1174
  result["mcp_config"], "[?]"
1034
1175
  )
@@ -1057,6 +1198,7 @@ def cmd_connect(args: Namespace) -> None:
1057
1198
  from superlocalmemory.hooks.ide_connector import IDEConnector
1058
1199
 
1059
1200
  connector = IDEConnector()
1201
+ dry_run = bool(getattr(args, "dry_run", False))
1060
1202
 
1061
1203
  if getattr(args, 'json', False):
1062
1204
  from superlocalmemory.cli.json_output import json_print
@@ -1068,6 +1210,14 @@ def cmd_connect(args: Namespace) -> None:
1068
1210
  elif getattr(args, "ide", None):
1069
1211
  success = connector.connect(args.ide)
1070
1212
  json_print("connect", data={"ide": args.ide, "connected": success})
1213
+ elif dry_run:
1214
+ json_print(
1215
+ "connect",
1216
+ data={
1217
+ "dry_run": True,
1218
+ "would_configure": connector.get_status(),
1219
+ },
1220
+ )
1071
1221
  else:
1072
1222
  json_print("connect", data={"results": connector.connect_all()},
1073
1223
  next_actions=[
@@ -1081,6 +1231,12 @@ def cmd_connect(args: Namespace) -> None:
1081
1231
  mark = "[+]" if s["installed"] else "[-]"
1082
1232
  print(f" {mark} {s['name']:20s} {s['config_path']}")
1083
1233
  return
1234
+ if dry_run:
1235
+ status = connector.get_status()
1236
+ print("Dry run — no IDE, hook, config, or SLM data files were written.")
1237
+ for item in status:
1238
+ print(f" would inspect/configure: {item['name']} ({item['config_path']})")
1239
+ return
1084
1240
  if getattr(args, "ide", None):
1085
1241
  success = connector.connect(args.ide)
1086
1242
  print(f"{'Connected' if success else 'Failed'}: {args.ide}")
@@ -1372,7 +1528,10 @@ def cmd_recall(args: Namespace) -> None:
1372
1528
 
1373
1529
  response = engine.recall(
1374
1530
  args.query, limit=args.limit,
1375
- fast=getattr(args, "fast", False),
1531
+ # v3.8.2: --fast → True; unset → None so engine resolves the
1532
+ # client-driven-agentic default (parity with the daemon path, which
1533
+ # omits the fast query param when --fast is absent).
1534
+ fast=(True if getattr(args, "fast", False) else None),
1376
1535
  include_global=include_global,
1377
1536
  include_shared=include_shared,
1378
1537
  window=getattr(args, "window", "") or None,
@@ -1563,16 +1722,86 @@ def cmd_forget(args: Namespace) -> None:
1563
1722
 
1564
1723
  def cmd_delete(args: Namespace) -> None:
1565
1724
  """Delete a specific memory by exact fact ID."""
1725
+ import urllib.parse
1726
+
1727
+ from superlocalmemory.cli.daemon import daemon_request, is_daemon_running
1566
1728
  from superlocalmemory.core.config import SLMConfig
1567
1729
  from superlocalmemory.core.engine import MemoryEngine
1568
1730
 
1569
1731
  use_json = getattr(args, 'json', False)
1732
+ fact_id = args.fact_id.strip()
1733
+ if is_daemon_running():
1734
+ path = "/api/memories/" + urllib.parse.quote(fact_id, safe="")
1735
+ confirmed = getattr(args, "yes", False)
1736
+ content = ""
1737
+ if not confirmed:
1738
+ detail = daemon_request(
1739
+ "GET",
1740
+ "/api/facts/" + urllib.parse.quote(fact_id, safe=""),
1741
+ )
1742
+ if not isinstance(detail, dict):
1743
+ if use_json:
1744
+ from superlocalmemory.cli.json_output import json_print
1745
+ json_print("delete", error={
1746
+ "code": "DAEMON_MUTATION_FAILED",
1747
+ "message": "Resident daemon could not resolve the memory.",
1748
+ })
1749
+ sys.exit(1)
1750
+ raise RuntimeError("Resident daemon could not resolve the memory.")
1751
+ content = str(detail.get("content") or "")
1752
+ if use_json:
1753
+ from superlocalmemory.cli.json_output import json_print
1754
+ json_print("delete", data={
1755
+ "fact_id": fact_id,
1756
+ "content": content[:120],
1757
+ "deleted": False,
1758
+ "hint": "Add --yes to confirm deletion",
1759
+ }, next_actions=[
1760
+ {
1761
+ "command": f"slm delete {fact_id} --json --yes",
1762
+ "description": "Confirm deletion",
1763
+ },
1764
+ ])
1765
+ return
1766
+ print(f"Memory: {content[:120]}")
1767
+ confirmed = input("Delete this memory? [y/N] ").strip().lower() in (
1768
+ "y", "yes",
1769
+ )
1770
+ if not confirmed:
1771
+ print("Cancelled.")
1772
+ return
1773
+
1774
+ result = daemon_request("DELETE", path)
1775
+ if not isinstance(result, dict) or not result.get("success"):
1776
+ if use_json:
1777
+ from superlocalmemory.cli.json_output import json_print
1778
+ json_print("delete", error={
1779
+ "code": "DAEMON_MUTATION_FAILED",
1780
+ "message": "Resident daemon rejected the delete operation.",
1781
+ })
1782
+ sys.exit(1)
1783
+ raise RuntimeError("Resident daemon rejected the delete operation.")
1784
+ if use_json:
1785
+ from superlocalmemory.cli.json_output import json_print
1786
+ json_print(
1787
+ "delete",
1788
+ data={"deleted": fact_id, "content": content[:120]},
1789
+ next_actions=[
1790
+ {
1791
+ "command": "slm list --json",
1792
+ "description": "Verify remaining memories",
1793
+ },
1794
+ ],
1795
+ )
1796
+ else:
1797
+ print(f"Deleted: {fact_id}")
1798
+ return
1799
+
1570
1800
  try:
1571
1801
  config = SLMConfig.load()
1572
1802
  engine = MemoryEngine(config)
1573
1803
  engine.initialize()
1574
1804
 
1575
- fact_id = args.fact_id.strip()
1576
1805
  rows = engine._db.execute(
1577
1806
  "SELECT content FROM atomic_facts WHERE fact_id = ? AND profile_id = ?",
1578
1807
  (fact_id, engine.profile_id),
@@ -1642,6 +1871,9 @@ def cmd_delete(args: Namespace) -> None:
1642
1871
 
1643
1872
  def cmd_update(args: Namespace) -> None:
1644
1873
  """Update the content of a specific memory by exact fact ID."""
1874
+ import urllib.parse
1875
+
1876
+ from superlocalmemory.cli.daemon import daemon_request, is_daemon_running
1645
1877
  from superlocalmemory.core.config import SLMConfig
1646
1878
  from superlocalmemory.core.engine import MemoryEngine
1647
1879
 
@@ -1657,6 +1889,34 @@ def cmd_update(args: Namespace) -> None:
1657
1889
  print("Error: content cannot be empty")
1658
1890
  return
1659
1891
 
1892
+ if is_daemon_running():
1893
+ path = "/api/memories/" + urllib.parse.quote(fact_id, safe="")
1894
+ result = daemon_request("PATCH", path, {"content": new_content})
1895
+ if not isinstance(result, dict) or not result.get("success"):
1896
+ if use_json:
1897
+ from superlocalmemory.cli.json_output import json_print
1898
+ json_print("update", error={
1899
+ "code": "DAEMON_MUTATION_FAILED",
1900
+ "message": "Resident daemon rejected the update operation.",
1901
+ })
1902
+ sys.exit(1)
1903
+ raise RuntimeError("Resident daemon rejected the update operation.")
1904
+ if use_json:
1905
+ from superlocalmemory.cli.json_output import json_print
1906
+ json_print("update", data={
1907
+ "fact_id": fact_id,
1908
+ "new_content": new_content[:120],
1909
+ }, next_actions=[
1910
+ {
1911
+ "command": "slm list --json",
1912
+ "description": "List recent memories",
1913
+ },
1914
+ ])
1915
+ else:
1916
+ print(f"New: {new_content[:100]}")
1917
+ print(f"Updated: {fact_id}")
1918
+ return
1919
+
1660
1920
  try:
1661
1921
  config = SLMConfig.load()
1662
1922
  engine = MemoryEngine(config)
@@ -2013,6 +2273,175 @@ def _readline_with_timeout(
2013
2273
  )
2014
2274
 
2015
2275
 
2276
+ # ---------------------------------------------------------------------------
2277
+ # Super-help (v3.8.2) — one place a non-technical user sees EVERYTHING.
2278
+ # Grouped so `slm help` is scannable; a drift test asserts every top-level
2279
+ # command in the parser appears here (see test_super_help).
2280
+ # ---------------------------------------------------------------------------
2281
+
2282
+ _COMMAND_GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
2283
+ ("Getting started", [
2284
+ ("setup", "Guided first-time setup (models, mode, IDEs) — start here"),
2285
+ ("init", "Set up + wire Claude Code hooks + connect IDEs"),
2286
+ ("reconfigure", "Re-run setup / pick a performance profile"),
2287
+ ("mode", "Switch memory mode: a (local) / b (Ollama) / c (cloud)"),
2288
+ ("provider", "Configure the cloud LLM provider + API key (Mode C)"),
2289
+ ("connect", "Auto-configure detected IDEs (Cursor, VS Code, …)"),
2290
+ ("hooks", "Install/inspect Claude Code hooks"),
2291
+ ("codex", "Configure the Codex / OpenAI integration"),
2292
+ ]),
2293
+ ("Store & recall memories", [
2294
+ ("remember", "Store a memory (--tags to label)"),
2295
+ ("recall", "Semantic + keyword search across memories"),
2296
+ ("search", "Exact keyword / full-text search"),
2297
+ ("list", "Show recent memories (-n N)"),
2298
+ ("update", "Correct an existing memory by id"),
2299
+ ("delete", "Delete a memory by id"),
2300
+ ("forget", "Run the decay cycle (preview first)"),
2301
+ ("trace", "Recall with a per-channel score breakdown"),
2302
+ ("ingest", "Ingest external observations / documents"),
2303
+ ]),
2304
+ ("Run SLM (daemon & dashboard)", [
2305
+ ("serve", "Start/stop the background daemon"),
2306
+ ("restart", "Restart the daemon (applies restart-only settings)"),
2307
+ ("dashboard", "Open the web dashboard (localhost:8765)"),
2308
+ ("mcp", "Start the MCP server (used by IDEs)"),
2309
+ ("warmup", "Pre-load models so the first recall is fast"),
2310
+ ("status", "Show daemon status + memory counts"),
2311
+ ]),
2312
+ ("Health & self-healing", [
2313
+ ("doctor", "Full pre-flight check (add --fix to auto-repair)"),
2314
+ ("health", "Quick math/retrieval layer status"),
2315
+ ("diagnostics", "Export a local diagnostics bundle"),
2316
+ ("evidence", "Build/inspect evidence bundles"),
2317
+ ("rotate-token", "Rotate the local dashboard install token"),
2318
+ ]),
2319
+ ("Configuration", [
2320
+ ("config", "View/set configuration (see: slm help config)"),
2321
+ ("enable", "Enable a feature/escape hatch"),
2322
+ ("disable", "Disable a feature/escape hatch"),
2323
+ ("clear-cache", "Clear caches"),
2324
+ ]),
2325
+ ("Profiles & sessions", [
2326
+ ("profile", "Manage workspaces/profiles (add, switch, list)"),
2327
+ ("session", "Open/close a work session"),
2328
+ ("session-context", "Pre-stage session context for hooks"),
2329
+ ]),
2330
+ ("Multi-device mesh", [
2331
+ ("mesh", "Inspect/manage the cross-device memory mesh"),
2332
+ ]),
2333
+ ("Token optimization", [
2334
+ ("optimize", "Configure the Optimize surface (cache/compress/proxy)"),
2335
+ ("cache", "Manage the LLM response cache"),
2336
+ ("compress", "Manage prose compression"),
2337
+ ("proxy", "Manage the optimizing LLM proxy"),
2338
+ ("wrap", "Run an agent (claude, codex, …) through the proxy"),
2339
+ ("help-optimize", "Detailed help for the Optimize surface"),
2340
+ ]),
2341
+ ("Learning & maintenance", [
2342
+ ("evolve", "Skill-evolution controls"),
2343
+ ("observe", "External observation / telemetry ingestion"),
2344
+ ("decay", "Run a forgetting/decay pass"),
2345
+ ("consolidate", "Merge/consolidate related memories"),
2346
+ ("quantize", "Quantize embeddings to save space"),
2347
+ ("soft-prompts", "Manage learned soft prompts"),
2348
+ ("reap", "Clean up stale/dead records"),
2349
+ ("adapters", "Manage ingestion adapters"),
2350
+ ("benchmark", "Run local benchmarks"),
2351
+ ]),
2352
+ ("Data & migration", [
2353
+ ("db", "Low-level database operations"),
2354
+ ("migrate", "Migrate the data store to the current version"),
2355
+ ]),
2356
+ ("Automation", [
2357
+ ("loop", "Run gate-verified bounded agent loops"),
2358
+ ]),
2359
+ ("Help", [
2360
+ ("help", "This overview. Try: slm help config | modes | self-heal"),
2361
+ ]),
2362
+ ]
2363
+
2364
+ _HELP_TOPICS: dict[str, str] = {
2365
+ "modes": """\
2366
+ Operating modes
2367
+ a Local Guardian — no model-provider call in the core memory path.
2368
+ b Smart Local — uses a local Ollama LLM (auto-detected at setup).
2369
+ c Full Power — uses a cloud LLM (OpenAI/Anthropic/…), needs a key.
2370
+
2371
+ Switch any time: slm mode a (or b / c)
2372
+ """,
2373
+ "config": """\
2374
+ Configuration
2375
+ slm config Show current configuration
2376
+ slm config get <key> Read one setting
2377
+ slm config set <key> <val> Change a setting
2378
+
2379
+ Most day-to-day settings are editable live in the dashboard
2380
+ (Settings tab) and apply without a restart. Settings that need a
2381
+ restart show a one-click "apply & restart" banner.
2382
+
2383
+ Common runtime settings: recall depth (top_k), memory injection on/off,
2384
+ stale-memory suppression, idle consolidation, PII redaction, reranker.
2385
+ """,
2386
+ "self-heal": """\
2387
+ Self-healing (v3.8.2)
2388
+ SLM repairs itself. On every start the daemon checks that all models
2389
+ and dependencies are present and re-downloads/installs the fixable ones
2390
+ in the background — you don't run anything.
2391
+
2392
+ See what's present/missing:
2393
+ slm doctor Full report with fix commands
2394
+ slm doctor --fix Auto-repair everything fixable, then report
2395
+ Dashboard → Help → System Health Live "what's missing" report
2396
+
2397
+ Big or opt-in pieces (the local LLM 'Ollama', the large compression
2398
+ model, PyTorch) are never installed without you — SLM shows the exact
2399
+ command instead.
2400
+ """,
2401
+ }
2402
+ # Aliases so common phrasings resolve to a topic.
2403
+ _HELP_TOPICS["health"] = _HELP_TOPICS["self-heal"]
2404
+ _HELP_TOPICS["selfheal"] = _HELP_TOPICS["self-heal"]
2405
+ _HELP_TOPICS["mode"] = _HELP_TOPICS["modes"]
2406
+
2407
+
2408
+ def all_help_commands() -> set[str]:
2409
+ """Every command name listed in the super-help (drift-test hook)."""
2410
+ return {cmd for _title, rows in _COMMAND_GROUPS for cmd, _desc in rows}
2411
+
2412
+
2413
+ def cmd_help(args: Namespace) -> None:
2414
+ """Super-help: a grouped overview of every command, plus focused topics.
2415
+
2416
+ `slm help` → grouped command overview
2417
+ `slm help <topic>` → modes | config | self-heal
2418
+ """
2419
+ topic = getattr(args, "topic", None)
2420
+ if topic:
2421
+ key = topic.strip().lower()
2422
+ text = _HELP_TOPICS.get(key)
2423
+ if text:
2424
+ print(text)
2425
+ else:
2426
+ print(f"No help topic '{topic}'. Available topics: "
2427
+ + ", ".join(sorted(set(_HELP_TOPICS))))
2428
+ return
2429
+
2430
+ from superlocalmemory.cli.json_output import _get_version
2431
+ print(f"SuperLocalMemory V3 ({_get_version()}) — command overview")
2432
+ print("=" * 58)
2433
+ print("Run any command with -h for its options, e.g. slm recall -h\n")
2434
+ for title, rows in _COMMAND_GROUPS:
2435
+ print(f"{title}:")
2436
+ for cmd, desc in rows:
2437
+ print(f" {cmd:<16} {desc}")
2438
+ print()
2439
+ print("Health at a glance: slm doctor (auto-repair: slm doctor --fix)")
2440
+ print("Topics: slm help modes | slm help config | "
2441
+ "slm help self-heal")
2442
+ print("Docs: https://superlocalmemory.com")
2443
+
2444
+
2016
2445
  def cmd_doctor(args: Namespace) -> None:
2017
2446
  """Comprehensive pre-flight check — verify everything works.
2018
2447
 
@@ -2050,6 +2479,34 @@ def cmd_doctor(args: Namespace) -> None:
2050
2479
  print("=" * 50)
2051
2480
  print()
2052
2481
 
2482
+ # v3.8.2 `--fix`: run the component healer (the SAME repair the daemon
2483
+ # self-heal performs) BEFORE the checks, so the report below reflects the
2484
+ # healed state. Re-downloads missing models / installs sqlite-vec; big or
2485
+ # opt-in items are left as manual fixes. Fail-open — never blocks doctor.
2486
+ if getattr(args, "fix", False):
2487
+ try:
2488
+ from superlocalmemory.core import component_healer
2489
+ from superlocalmemory.core.config import SLMConfig as _FixCfg
2490
+ try:
2491
+ _fcfg = _FixCfg.load()
2492
+ except Exception:
2493
+ _fcfg = None
2494
+ if not use_json:
2495
+ print("Auto-repair (--fix): repairing fixable components…")
2496
+ _fix_res = component_healer.heal_missing(
2497
+ _fcfg,
2498
+ on_progress=(None if use_json
2499
+ else (lambda k, m: print(f" [{k}] {m}"))),
2500
+ )
2501
+ if not use_json:
2502
+ print(
2503
+ f" repaired {_fix_res['healed']}/{_fix_res['attempted']} "
2504
+ f"(failed {_fix_res['failed']})\n"
2505
+ )
2506
+ except Exception as _fix_exc:
2507
+ if not use_json:
2508
+ print(f" auto-repair error: {_fix_exc}\n")
2509
+
2053
2510
  # 1. Python version
2054
2511
  v = sys.version_info
2055
2512
  if v >= (3, 11):
@@ -2315,6 +2772,34 @@ def cmd_doctor(args: Namespace) -> None:
2315
2772
  except Exception:
2316
2773
  pass # advisory only — never fail doctor on this check
2317
2774
 
2775
+ # 12a. Component registry (v3.8.2) — models + optional vector/graph/
2776
+ # compression backends the older checks above did NOT cover
2777
+ # (embedder/reranker/compressor cache presence, sqlite-vec, lancedb,
2778
+ # cozo, llmlingua). Single source of truth shared with the daemon
2779
+ # self-heal and the dashboard "what's missing" report. Missing items
2780
+ # are advisory (WARN), never FAIL — the self-heal thread and
2781
+ # `slm doctor --fix` repair the auto-fixable ones. Runs BEFORE the
2782
+ # Optimize check so that check stays last (its ordering is pinned by
2783
+ # test_doctor_optimize).
2784
+ try:
2785
+ from superlocalmemory.core import component_registry as _cr
2786
+ try:
2787
+ from superlocalmemory.core.config import SLMConfig as _Cfg
2788
+ _dcfg = _Cfg.load()
2789
+ except Exception:
2790
+ _dcfg = None
2791
+ _already = {"python", "search_deps", "ollama"}
2792
+ for _c in _cr.probe_all(_dcfg):
2793
+ if _c.key in _already:
2794
+ continue # covered by checks 1/3/8 above — avoid duplicates
2795
+ _status = "PASS" if _c.status == _cr.STATUS_OK else "WARN"
2796
+ _detail = _c.detail or _c.status
2797
+ if _c.auto_fixable:
2798
+ _detail += " (auto-heals on daemon start)"
2799
+ _check(f"Component: {_c.label}", _status, _detail, _c.fix_cmd)
2800
+ except Exception as _cr_exc:
2801
+ _check("Component registry", "WARN", f"probe failed: {_cr_exc}")
2802
+
2318
2803
  # 12. Optimize (Surface B) — reads daemon-persisted metrics (≤60s stale).
2319
2804
  info = _gather_optimize_surface_b()
2320
2805
  _enabled = info["enabled"]