superlocalmemory 3.8.0 → 3.8.1

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 (118) hide show
  1. package/CHANGELOG.md +60 -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 +1 -1
  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 +1 -1
  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 +1 -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 +1 -1
  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/src/superlocalmemory/__init__.py +1 -1
  33. package/src/superlocalmemory/cli/commands.py +134 -7
  34. package/src/superlocalmemory/cli/daemon.py +7 -0
  35. package/src/superlocalmemory/cli/loop_cmd.py +2 -7
  36. package/src/superlocalmemory/cli/main.py +10 -4
  37. package/src/superlocalmemory/cli/version_banner.py +17 -3
  38. package/src/superlocalmemory/core/backend_orchestrator.py +18 -16
  39. package/src/superlocalmemory/core/embedding_worker.py +4 -5
  40. package/src/superlocalmemory/core/embeddings.py +119 -45
  41. package/src/superlocalmemory/core/engine.py +24 -21
  42. package/src/superlocalmemory/core/engine_ingestion.py +332 -45
  43. package/src/superlocalmemory/core/ingestion_command.py +134 -25
  44. package/src/superlocalmemory/core/injection.py +12 -7
  45. package/src/superlocalmemory/core/maintenance_scheduler.py +17 -7
  46. package/src/superlocalmemory/core/recall_pipeline.py +5 -3
  47. package/src/superlocalmemory/core/store_pipeline.py +153 -20
  48. package/src/superlocalmemory/hooks/hook_handlers.py +6 -1
  49. package/src/superlocalmemory/hooks/portable_kit.py +34 -2
  50. package/src/superlocalmemory/learning/model_rollback.py +3 -0
  51. package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
  52. package/src/superlocalmemory/learning/reward.py +50 -0
  53. package/src/superlocalmemory/learning/source_quality.py +523 -1
  54. package/src/superlocalmemory/loops/ledger.py +25 -5
  55. package/src/superlocalmemory/mcp/server.py +11 -30
  56. package/src/superlocalmemory/mcp/tools_core.py +4 -3
  57. package/src/superlocalmemory/mcp/tools_learning.py +2 -2
  58. package/src/superlocalmemory/retrieval/engine.py +53 -21
  59. package/src/superlocalmemory/retrieval/reranker.py +3 -4
  60. package/src/superlocalmemory/server/config_file.py +90 -0
  61. package/src/superlocalmemory/server/origin.py +50 -0
  62. package/src/superlocalmemory/server/routes/backup.py +293 -70
  63. package/src/superlocalmemory/server/routes/behavioral.py +336 -59
  64. package/src/superlocalmemory/server/routes/brain.py +57 -16
  65. package/src/superlocalmemory/server/routes/config_api.py +84 -82
  66. package/src/superlocalmemory/server/routes/entity.py +100 -23
  67. package/src/superlocalmemory/server/routes/evolution.py +103 -100
  68. package/src/superlocalmemory/server/routes/learning.py +276 -105
  69. package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
  70. package/src/superlocalmemory/server/routes/mesh.py +121 -32
  71. package/src/superlocalmemory/server/routes/ratelimit.py +33 -25
  72. package/src/superlocalmemory/server/routes/stats.py +93 -155
  73. package/src/superlocalmemory/server/routes/token.py +3 -13
  74. package/src/superlocalmemory/server/routes/v3_api.py +64 -20
  75. package/src/superlocalmemory/server/unified_daemon.py +467 -40
  76. package/src/superlocalmemory/storage/migration_runner.py +79 -1
  77. package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -0
  78. package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +270 -0
  79. package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
  80. package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
  81. package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
  82. package/src/superlocalmemory/storage/schema.py +49 -1
  83. package/src/superlocalmemory/storage/schema_v32.py +2 -0
  84. package/src/superlocalmemory/storage/schema_v347.py +4 -0
  85. package/src/superlocalmemory/ui/index.html +3 -6
  86. package/src/superlocalmemory/ui/js/core.js +52 -9
  87. package/src/superlocalmemory/ui/js/dashboard.js +169 -82
  88. package/src/superlocalmemory/ui/js/od-backup.js +156 -65
  89. package/src/superlocalmemory/ui/js/od-brain.js +88 -51
  90. package/src/superlocalmemory/ui/js/od-entities.js +22 -22
  91. package/src/superlocalmemory/ui/js/od-graph.js +11 -4
  92. package/src/superlocalmemory/ui/js/od-memories.js +47 -5
  93. package/src/superlocalmemory/ui/js/od-mesh.js +23 -9
  94. package/src/superlocalmemory/ui/js/od-settings.js +113 -59
  95. package/src/superlocalmemory/ui/js/od-shell.js +249 -33
  96. package/src/superlocalmemory/ui/js/od-skills.js +44 -17
  97. package/src/superlocalmemory/ui/js/settings.js +15 -1
  98. package/plugin-src/.mcp.json +0 -12
  99. package/plugin-src/agents/slm-governance-advisor.md +0 -80
  100. package/plugin-src/agents/slm-loop-runner.md +0 -71
  101. package/plugin-src/agents/slm-memory-advisor.md +0 -49
  102. package/plugin-src/agents/slm-optimize-advisor.md +0 -44
  103. package/plugin-src/commands/slm-loop.md +0 -31
  104. package/plugin-src/hooks/.gitkeep +0 -0
  105. package/plugin-src/hooks/hooks.json +0 -102
  106. package/plugin-src/manifest.json +0 -30
  107. package/plugin-src/requirements.txt +0 -1
  108. package/plugin-src/rules/CLAUDE.md.fragment +0 -44
  109. package/plugin-src/scripts/ensure-venv.bat +0 -122
  110. package/plugin-src/scripts/ensure-venv.sh +0 -105
  111. package/plugin-src/scripts/slm-launch +0 -62
  112. package/plugin-src/scripts/slm-launch.bat +0 -23
  113. package/plugin-src/settings.json +0 -25
  114. package/plugin-src/skills/slm-governance/SKILL.md +0 -248
  115. package/plugin-src/skills/slm-loop/SKILL.md +0 -99
  116. package/plugin-src/skills/slm-mesh/SKILL.md +0 -282
  117. package/plugin-src/skills/slm-profile/SKILL.md +0 -148
  118. package/plugin-src/skills/slm-scope/SKILL.md +0 -176
@@ -240,11 +240,12 @@ def connect_ide(
240
240
  here: bool = False,
241
241
  profile: str | None = None,
242
242
  agents_md_source: Callable[[], str] | None = None,
243
+ dry_run: bool = False,
243
244
  ) -> dict[str, Any]:
244
245
  """Wire SLM into the target IDE config via merge-not-clobber.
245
246
 
246
247
  Returns a result dict:
247
- {ide, mcp_config: wrote|merged|unchanged|skipped|error,
248
+ {ide, mcp_config: wrote|merged|unchanged|would_write|skipped|error,
248
249
  mcp_path, agents_md: wrote|skipped(...)|unchanged|error,
249
250
  servers_preserved: int, error: str|None}
250
251
  """
@@ -265,6 +266,13 @@ def connect_ide(
265
266
  )
266
267
  return result
267
268
 
269
+ if ide_id == "vscode-copilot" and not here:
270
+ result["error"] = (
271
+ "VS Code / Copilot integration is project-scoped. "
272
+ "Run `slm connect vscode-copilot --here` from the project root."
273
+ )
274
+ return result
275
+
268
276
  # Step 1a — claude-code short-circuit (AC6)
269
277
  if desc.fmt == "":
270
278
  print(CLAUDE_CODE_PLUGIN_POINTER)
@@ -325,7 +333,28 @@ def connect_ide(
325
333
  result["servers_preserved"] = max(0, pre_count - (0 if pre_slm is None else 1))
326
334
  result["mcp_config"] = mcp_status
327
335
 
328
- # Step 6 — atomic write
336
+ # Step 6 — atomic write. A dry run deliberately exercises the merge and
337
+ # packaged-asset lookup path without touching a user-owned IDE config.
338
+ if dry_run:
339
+ result["mcp_config"] = "would_write"
340
+ if desc.agents_md_path is None:
341
+ result["agents_md"] = "skipped(unsupported)"
342
+ elif agents_md_source is None:
343
+ result["agents_md"] = "skipped(no-source)"
344
+ else:
345
+ try:
346
+ source_content = agents_md_source()
347
+ except Exception as exc:
348
+ result["agents_md"] = "error(source-unavailable)"
349
+ result["error"] = f"AGENTS.md asset lookup failed: {exc}"
350
+ return result
351
+ if not isinstance(source_content, str) or not source_content.strip():
352
+ result["agents_md"] = "error(empty-source)"
353
+ result["error"] = "AGENTS.md asset is empty"
354
+ return result
355
+ result["agents_md"] = "would_write"
356
+ return result
357
+
329
358
  try:
330
359
  _atomic_write(config_path, data, desc.fmt)
331
360
  except Exception as exc:
@@ -528,6 +557,9 @@ def _handle_agents_md(
528
557
  except Exception as exc:
529
558
  logger.warning("agents_md_source() failed: %s — skipping AGENTS.md write", exc)
530
559
  return "skipped(source-error)"
560
+ if not isinstance(source_content, str) or not source_content.strip():
561
+ logger.warning("agents_md_source() returned no content — skipping AGENTS.md write")
562
+ return "skipped(no-source-content)"
531
563
 
532
564
  # Read existing content
533
565
  existing = ""
@@ -34,6 +34,8 @@ import sqlite3
34
34
  from datetime import datetime, timedelta, timezone
35
35
  from typing import Final
36
36
 
37
+ from superlocalmemory.learning.model_cache import invalidate as invalidate_model_cache
38
+
37
39
  logger = logging.getLogger(__name__)
38
40
 
39
41
 
@@ -235,6 +237,7 @@ class ModelRollback:
235
237
  )
236
238
  return False
237
239
 
240
+ invalidate_model_cache(self._profile_id)
238
241
  logger.warning(
239
242
  "AUTO-ROLLBACK profile=%s reason=%s observations=%d "
240
243
  "baseline_ndcg=%.4f current_ndcg=%.4f",
@@ -33,6 +33,7 @@ from datetime import datetime, timezone
33
33
  from pathlib import Path
34
34
  from typing import Final
35
35
 
36
+ from superlocalmemory.learning.model_cache import invalidate as invalidate_model_cache
36
37
  from superlocalmemory.learning.ranker_common import (
37
38
  _build_training_matrix,
38
39
  _compute_eval_metrics,
@@ -389,6 +390,7 @@ def _promote_candidate(
389
390
  (json.dumps(meta), candidate_id),
390
391
  )
391
392
  conn.commit()
393
+ invalidate_model_cache(profile_id)
392
394
  return True
393
395
  except sqlite3.Error as exc:
394
396
  conn.rollback()
@@ -73,6 +73,24 @@ _DWELL_MAX_MS: Final[int] = 3_600_000 # 1 h
73
73
  _BUSY_TIMEOUT_MS: Final[int] = 50
74
74
 
75
75
 
76
+ def _feed_source_quality(
77
+ memory_db_path: Path,
78
+ rewards: list[tuple[str, str, list[str], float]],
79
+ ) -> None:
80
+ """Fail-soft provenance bridge kept outside the reward transaction."""
81
+ try:
82
+ from superlocalmemory.learning.source_quality import (
83
+ update_source_quality_for_reward_batch,
84
+ )
85
+ update_source_quality_for_reward_batch(
86
+ memory_db_path=memory_db_path,
87
+ learning_db_path=memory_db_path.parent / "learning.db",
88
+ rewards=rewards,
89
+ )
90
+ except Exception as exc: # noqa: BLE001 - reward finalization must survive
91
+ logger.debug("source-quality reward feed failed (non-fatal): %s", exc)
92
+
93
+
76
94
  # ---------------------------------------------------------------------------
77
95
  # Signal contract — names match the manifest A.1 label formula.
78
96
  # ---------------------------------------------------------------------------
@@ -604,6 +622,7 @@ class EngagementRewardModel:
604
622
  # lock-scope — SQLite rows are tied to the connection.
605
623
  _pid = pending["profile_id"]
606
624
  _qid = pending["recall_query_id"]
625
+ _facts_json = str(pending["fact_ids_json"] or "[]")
607
626
  except sqlite3.Error as exc:
608
627
  logger.debug("finalize_outcome SQLite error: %s", exc)
609
628
  return _FALLBACK_REWARD
@@ -633,6 +652,21 @@ class EngagementRewardModel:
633
652
  except Exception as exc: # noqa: BLE001 — defence in depth
634
653
  logger.debug("feed_recall_settled failed (non-fatal): %s", exc)
635
654
 
655
+ try:
656
+ _fact_ids = json.loads(_facts_json)
657
+ if not isinstance(_fact_ids, list):
658
+ _fact_ids = []
659
+ except (TypeError, ValueError, json.JSONDecodeError):
660
+ _fact_ids = []
661
+ _feed_source_quality(
662
+ self._db,
663
+ [(
664
+ str(_pid),
665
+ str(outcome_id),
666
+ [str(fid) for fid in _fact_ids if fid],
667
+ float(reward),
668
+ )],
669
+ )
636
670
  return reward
637
671
 
638
672
  # ------------------------------------------------------------------
@@ -749,6 +783,22 @@ class EngagementRewardModel:
749
783
  except sqlite3.Error:
750
784
  conn.execute("ROLLBACK")
751
785
  raise
786
+ reward_sources = []
787
+ for item in i_chunk:
788
+ try:
789
+ fact_ids = json.loads(str(item[2] or "[]"))
790
+ except (TypeError, ValueError, json.JSONDecodeError):
791
+ fact_ids = []
792
+ reward_sources.append((
793
+ str(item[1]),
794
+ str(item[0]),
795
+ (
796
+ [str(fid) for fid in fact_ids if fid]
797
+ if isinstance(fact_ids, list) else []
798
+ ),
799
+ float(item[4]),
800
+ ))
801
+ _feed_source_quality(self._db, reward_sources)
752
802
  except sqlite3.Error as exc: # pragma: no cover — defensive
753
803
  logger.debug("reap_stale SQLite error: %s", exc)
754
804
  return written