superlocalmemory 3.6.13 → 3.6.14
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.
- package/.claude-plugin/marketplace.json +17 -0
- package/README.md +187 -741
- package/package.json +12 -5
- package/plugin/.claude-plugin/plugin.json +20 -0
- package/plugin/.mcp.json +12 -0
- package/plugin/CLAUDE.md +43 -0
- package/plugin/_GENERATED.md +6 -0
- package/plugin/agents/slm-memory-advisor.md +43 -0
- package/plugin/agents/slm-optimize-advisor.md +38 -0
- package/plugin/hooks/hooks.json +14 -0
- package/plugin/requirements.txt +1 -0
- package/plugin/scripts/ensure-venv.bat +122 -0
- package/plugin/scripts/ensure-venv.sh +105 -0
- package/plugin/scripts/slm-launch +15 -0
- package/plugin/scripts/slm-launch.bat +17 -0
- package/plugin/settings.json +16 -0
- package/plugin/skills/slm-cache/SKILL.md +140 -0
- package/plugin/skills/slm-compress/SKILL.md +143 -0
- package/plugin/skills/slm-graph/SKILL.md +300 -0
- package/plugin/skills/slm-recall/SKILL.md +196 -0
- package/plugin/skills/slm-remember/SKILL.md +182 -0
- package/plugin/skills/slm-session/SKILL.md +207 -0
- package/plugin/skills/slm-status/SKILL.md +149 -0
- package/plugin-src/.mcp.json +12 -0
- package/plugin-src/agents/slm-memory-advisor.md +43 -0
- package/plugin-src/agents/slm-optimize-advisor.md +38 -0
- package/plugin-src/commands/slm-optimize.md +22 -0
- package/plugin-src/commands/slm-recall.md +16 -0
- package/plugin-src/commands/slm-remember.md +16 -0
- package/plugin-src/commands/slm-status.md +15 -0
- package/plugin-src/hooks/.gitkeep +0 -0
- package/plugin-src/hooks/hooks.json +14 -0
- package/plugin-src/manifest.json +25 -0
- package/plugin-src/requirements.txt +1 -0
- package/plugin-src/rules/AGENTS.md +90 -0
- package/plugin-src/rules/CLAUDE.md.fragment +43 -0
- package/plugin-src/scripts/ensure-venv.bat +122 -0
- package/plugin-src/scripts/ensure-venv.sh +105 -0
- package/plugin-src/scripts/slm-launch +15 -0
- package/plugin-src/scripts/slm-launch.bat +17 -0
- package/plugin-src/settings.json +16 -0
- package/plugin-src/skills/slm-cache/SKILL.md +140 -0
- package/plugin-src/skills/slm-compress/SKILL.md +143 -0
- package/plugin-src/skills/slm-graph/SKILL.md +300 -0
- package/plugin-src/skills/slm-recall/SKILL.md +196 -0
- package/plugin-src/skills/slm-remember/SKILL.md +182 -0
- package/plugin-src/skills/slm-session/SKILL.md +207 -0
- package/plugin-src/skills/slm-status/SKILL.md +149 -0
- package/pyproject.toml +6 -2
- package/scripts/__tests__/build-plugin.test.mjs +613 -0
- package/scripts/_savings_math.py +270 -0
- package/scripts/build-plugin.js +742 -0
- package/scripts/dogfood_savings.py +490 -0
- package/scripts/install-skills.ps1 +4 -334
- package/scripts/install-skills.sh +4 -435
- package/scripts/postinstall-interactive.js +0 -27
- package/scripts/postinstall.js +21 -2
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/_lazy_init.py +115 -0
- package/src/superlocalmemory/cli/commands.py +348 -39
- package/src/superlocalmemory/cli/main.py +47 -4
- package/src/superlocalmemory/cli/setup_wizard.py +20 -6
- package/src/superlocalmemory/core/config.py +79 -9
- package/src/superlocalmemory/core/embeddings.py +10 -5
- package/src/superlocalmemory/core/engine.py +2 -2
- package/src/superlocalmemory/hooks/claude_code_hooks.py +27 -3
- package/src/superlocalmemory/hooks/portable_kit.py +506 -0
- package/src/superlocalmemory/infra/cloud_backup.py +99 -23
- package/src/superlocalmemory/mcp/cli_fallback.py +602 -0
- package/src/superlocalmemory/mcp/server.py +75 -4
- package/src/superlocalmemory/mcp/tools_code_graph.py +3 -3
- package/src/superlocalmemory/mcp/tools_core.py +12 -4
- package/src/superlocalmemory/optimize/cache/boundary_store.py +25 -6
- package/src/superlocalmemory/optimize/cache/centroid_store.py +27 -4
- package/src/superlocalmemory/optimize/cache/manager.py +92 -6
- package/src/superlocalmemory/optimize/cache/semantic.py +20 -1
- package/src/superlocalmemory/optimize/compress/ccr.py +12 -0
- package/src/superlocalmemory/optimize/compress/router.py +46 -13
- package/src/superlocalmemory/optimize/config/schema.py +6 -0
- package/src/superlocalmemory/optimize/proxy/_helpers.py +111 -8
- package/src/superlocalmemory/optimize/proxy/anthropic_surface.py +14 -4
- package/src/superlocalmemory/optimize/proxy/gemini_surface.py +23 -6
- package/src/superlocalmemory/optimize/proxy/openai_surface.py +10 -4
- package/src/superlocalmemory/optimize/proxy/server.py +11 -0
- package/src/superlocalmemory/optimize/proxy/vertex_surface.py +246 -0
- package/src/superlocalmemory/optimize/storage/db.py +30 -0
- package/src/superlocalmemory/server/recall_serializer.py +3 -1
- package/src/superlocalmemory/server/unified_daemon.py +24 -6
- package/src/superlocalmemory/ui/css/legacy-dashboard.css +18 -0
- package/src/superlocalmemory/ui/css/neural-glass.css +5 -0
- package/src/superlocalmemory/ui/index.html +2 -2
- package/src/superlocalmemory/ui/js/core.js +98 -0
- package/src/superlocalmemory/ui/js/dashboard.js +8 -1
- package/src/superlocalmemory/ui/js/ide-status.js +16 -3
- package/src/superlocalmemory/ui/js/math-health.js +15 -3
- package/src/superlocalmemory/ui/js/optimize.js +18 -2
- package/src/superlocalmemory/ui/js/trust-dashboard.js +10 -1
- package/src/superlocalmemory.egg-info/PKG-INFO +189 -742
- package/src/superlocalmemory.egg-info/SOURCES.txt +6 -9
- package/src/superlocalmemory.egg-info/requires.txt +1 -0
- package/ide/skills/slm-build-graph/SKILL.md +0 -423
- package/ide/skills/slm-list-recent/SKILL.md +0 -348
- package/ide/skills/slm-recall/SKILL.md +0 -326
- package/ide/skills/slm-remember/SKILL.md +0 -194
- package/ide/skills/slm-show-patterns/SKILL.md +0 -224
- package/ide/skills/slm-status/SKILL.md +0 -363
- package/ide/skills/slm-switch-profile/SKILL.md +0 -442
- package/skills/slm-build-graph/SKILL.md +0 -423
- package/skills/slm-list-recent/SKILL.md +0 -348
- package/skills/slm-optimize/README.md +0 -55
- package/skills/slm-optimize/SKILL.md +0 -139
- package/skills/slm-recall/SKILL.md +0 -343
- package/skills/slm-remember/SKILL.md +0 -194
- package/skills/slm-show-patterns/SKILL.md +0 -224
- package/skills/slm-status/SKILL.md +0 -363
- package/skills/slm-switch-profile/SKILL.md +0 -442
- package/src/superlocalmemory/cli/doctor_cmd.py +0 -152
- package/src/superlocalmemory/skills/slm-build-graph/SKILL.md +0 -423
- package/src/superlocalmemory/skills/slm-list-recent/SKILL.md +0 -348
- package/src/superlocalmemory/skills/slm-recall/SKILL.md +0 -343
- package/src/superlocalmemory/skills/slm-remember/SKILL.md +0 -194
- package/src/superlocalmemory/skills/slm-show-patterns/SKILL.md +0 -224
- package/src/superlocalmemory/skills/slm-status/SKILL.md +0 -363
- package/src/superlocalmemory/skills/slm-switch-profile/SKILL.md +0 -442
|
@@ -21,6 +21,16 @@ logger = logging.getLogger(__name__)
|
|
|
21
21
|
from superlocalmemory.storage.models import Mode
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# Canonical limits (WP-02 — single source of truth across all surfaces)
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
#: Default number of results returned by recall across MCP, CLI, daemon, and
|
|
29
|
+
#: engine. All surfaces bind their ``limit`` defaults to this constant so a
|
|
30
|
+
#: single change is sufficient to keep the full stack in sync.
|
|
31
|
+
CANONICAL_RECALL_LIMIT: int = 20
|
|
32
|
+
|
|
33
|
+
|
|
24
34
|
# ---------------------------------------------------------------------------
|
|
25
35
|
# Default Paths
|
|
26
36
|
# ---------------------------------------------------------------------------
|
|
@@ -739,17 +749,32 @@ class SLMConfig:
|
|
|
739
749
|
@classmethod
|
|
740
750
|
def load(cls, config_path: Path | None = None) -> SLMConfig:
|
|
741
751
|
"""Load config from JSON file. Returns default Mode A if file doesn't exist."""
|
|
742
|
-
|
|
752
|
+
# WP-07: resolve base dir through slm_home() at call time so all 3 env
|
|
753
|
+
# aliases (SLM_DATA_DIR → SL_MEMORY_PATH → SLM_HOME) are honoured.
|
|
754
|
+
try:
|
|
755
|
+
from superlocalmemory.cli._lazy_init import slm_home as _slm_home
|
|
756
|
+
_runtime_base = _slm_home()
|
|
757
|
+
except Exception:
|
|
758
|
+
_runtime_base = DEFAULT_BASE_DIR
|
|
759
|
+
path = config_path or (_runtime_base / "config.json")
|
|
743
760
|
if not path.exists():
|
|
744
761
|
return cls.for_mode(Mode.A)
|
|
745
762
|
import json
|
|
746
|
-
|
|
763
|
+
try:
|
|
764
|
+
data = json.loads(path.read_text())
|
|
765
|
+
except (json.JSONDecodeError, OSError) as exc:
|
|
766
|
+
# An already-corrupt/truncated config.json must NOT brick every `slm`
|
|
767
|
+
# call — degrade to the Mode-A default and warn rather than raise.
|
|
768
|
+
logger.warning(
|
|
769
|
+
"config.json unreadable/corrupt (%s) — using Mode A default", exc
|
|
770
|
+
)
|
|
771
|
+
return cls.for_mode(Mode.A)
|
|
747
772
|
mode = Mode(data.get("mode", "a"))
|
|
748
773
|
llm_data = data.get("llm", {})
|
|
749
774
|
emb_data = data.get("embedding", {})
|
|
750
775
|
# V3.5.9: read base_dir before constructing config so for_mode() builds
|
|
751
776
|
# db_path from the user's directory, not DEFAULT_BASE_DIR.
|
|
752
|
-
raw_base_dir = Path(data.get("base_dir", str(
|
|
777
|
+
raw_base_dir = Path(data.get("base_dir", str(_runtime_base)))
|
|
753
778
|
config = cls.for_mode(
|
|
754
779
|
mode,
|
|
755
780
|
llm_provider=llm_data.get("provider", ""),
|
|
@@ -932,7 +957,12 @@ class SLMConfig:
|
|
|
932
957
|
if key in existing:
|
|
933
958
|
data[key] = existing[key]
|
|
934
959
|
|
|
935
|
-
|
|
960
|
+
# Atomic write: a crash mid-write must NOT leave a truncated/corrupt
|
|
961
|
+
# config.json (which would make every subsequent `slm` call fail to load).
|
|
962
|
+
import os as _os
|
|
963
|
+
_tmp = path.with_suffix(path.suffix + ".tmp")
|
|
964
|
+
_tmp.write_text(json.dumps(data, indent=2))
|
|
965
|
+
_os.replace(_tmp, path)
|
|
936
966
|
|
|
937
967
|
@staticmethod
|
|
938
968
|
def provider_presets() -> dict[str, dict[str, str]]:
|
|
@@ -987,7 +1017,15 @@ class SLMConfig:
|
|
|
987
1017
|
embedding_dimension: int = 0,
|
|
988
1018
|
) -> SLMConfig:
|
|
989
1019
|
"""Create config with mode-appropriate defaults."""
|
|
990
|
-
|
|
1020
|
+
# WP-07: resolve base dir via slm_home() at call time when not explicit.
|
|
1021
|
+
if base_dir is None:
|
|
1022
|
+
try:
|
|
1023
|
+
from superlocalmemory.cli._lazy_init import slm_home as _slm_home
|
|
1024
|
+
_base = _slm_home()
|
|
1025
|
+
except Exception:
|
|
1026
|
+
_base = DEFAULT_BASE_DIR
|
|
1027
|
+
else:
|
|
1028
|
+
_base = base_dir
|
|
991
1029
|
|
|
992
1030
|
if mode == Mode.A:
|
|
993
1031
|
# V3.4.24: If user chose "openai" provider, honour their custom
|
|
@@ -1130,7 +1168,15 @@ class SLMConfig:
|
|
|
1130
1168
|
|
|
1131
1169
|
Returns ``\"b\"`` (the default) if the file doesn't exist.
|
|
1132
1170
|
"""
|
|
1133
|
-
|
|
1171
|
+
# WP-07: resolve via slm_home() at call time when base_dir not explicit.
|
|
1172
|
+
if base_dir is None:
|
|
1173
|
+
try:
|
|
1174
|
+
from superlocalmemory.cli._lazy_init import slm_home as _slm_home
|
|
1175
|
+
_base = _slm_home()
|
|
1176
|
+
except Exception:
|
|
1177
|
+
_base = DEFAULT_BASE_DIR
|
|
1178
|
+
else:
|
|
1179
|
+
_base = base_dir
|
|
1134
1180
|
_f = _base / CURRENT_MODE_FILE
|
|
1135
1181
|
try:
|
|
1136
1182
|
return _f.read_text(encoding="utf-8").strip().lower() or "b"
|
|
@@ -1140,7 +1186,15 @@ class SLMConfig:
|
|
|
1140
1186
|
@staticmethod
|
|
1141
1187
|
def write_current_mode(mode: str, base_dir: Path | None = None) -> None:
|
|
1142
1188
|
"""Write the current mode letter to ``current_mode``."""
|
|
1143
|
-
|
|
1189
|
+
# WP-07: resolve via slm_home() at call time when base_dir not explicit.
|
|
1190
|
+
if base_dir is None:
|
|
1191
|
+
try:
|
|
1192
|
+
from superlocalmemory.cli._lazy_init import slm_home as _slm_home
|
|
1193
|
+
_base = _slm_home()
|
|
1194
|
+
except Exception:
|
|
1195
|
+
_base = DEFAULT_BASE_DIR
|
|
1196
|
+
else:
|
|
1197
|
+
_base = base_dir
|
|
1144
1198
|
_base.mkdir(parents=True, exist_ok=True)
|
|
1145
1199
|
(_base / CURRENT_MODE_FILE).write_text(
|
|
1146
1200
|
mode.lower().strip(), encoding="utf-8",
|
|
@@ -1175,7 +1229,15 @@ class SLMConfig:
|
|
|
1175
1229
|
import dataclasses
|
|
1176
1230
|
|
|
1177
1231
|
from superlocalmemory.storage.models import Mode as _M
|
|
1178
|
-
|
|
1232
|
+
# WP-07: resolve via slm_home() at call time when base_dir not explicit.
|
|
1233
|
+
if base_dir is None:
|
|
1234
|
+
try:
|
|
1235
|
+
from superlocalmemory.cli._lazy_init import slm_home as _slm_home
|
|
1236
|
+
_base = _slm_home()
|
|
1237
|
+
except Exception:
|
|
1238
|
+
_base = DEFAULT_BASE_DIR
|
|
1239
|
+
else:
|
|
1240
|
+
_base = base_dir
|
|
1179
1241
|
_base.mkdir(parents=True, exist_ok=True)
|
|
1180
1242
|
|
|
1181
1243
|
old_config = cls.load(_base / "config.json")
|
|
@@ -1242,7 +1304,15 @@ class SLMConfig:
|
|
|
1242
1304
|
Called on daemon boot. Idempotent — if ``current_mode`` already
|
|
1243
1305
|
exists, this is a no-op. Returns True if migration was performed.
|
|
1244
1306
|
"""
|
|
1245
|
-
|
|
1307
|
+
# WP-07: resolve via slm_home() at call time when base_dir not explicit.
|
|
1308
|
+
if base_dir is None:
|
|
1309
|
+
try:
|
|
1310
|
+
from superlocalmemory.cli._lazy_init import slm_home as _slm_home
|
|
1311
|
+
_base = _slm_home()
|
|
1312
|
+
except Exception:
|
|
1313
|
+
_base = DEFAULT_BASE_DIR
|
|
1314
|
+
else:
|
|
1315
|
+
_base = base_dir
|
|
1246
1316
|
_current = _base / CURRENT_MODE_FILE
|
|
1247
1317
|
if _current.exists():
|
|
1248
1318
|
return False # already migrated
|
|
@@ -601,8 +601,13 @@ class EmbeddingService:
|
|
|
601
601
|
def _cloud_embed_batch(
|
|
602
602
|
self, texts: list[str], *, max_retries: int = 3,
|
|
603
603
|
) -> list[list[float]]:
|
|
604
|
-
"""Encode via Azure OpenAI embedding API with retry.
|
|
605
|
-
|
|
604
|
+
"""Encode via Azure OpenAI embedding API with retry.
|
|
605
|
+
|
|
606
|
+
V3.6.14: Reuses self._get_http_client() (shared persistent connection)
|
|
607
|
+
instead of creating a fresh httpx.Client per call/retry. resp.json()
|
|
608
|
+
is now called inside the try block while the response object is still
|
|
609
|
+
in scope, eliminating reliance on httpx body-buffering after close.
|
|
610
|
+
"""
|
|
606
611
|
url = (
|
|
607
612
|
f"{self._config.api_endpoint.rstrip('/')}/openai/deployments/"
|
|
608
613
|
f"{self._config.deployment_name}/embeddings"
|
|
@@ -613,12 +618,12 @@ class EmbeddingService:
|
|
|
613
618
|
"api-key": self._config.api_key,
|
|
614
619
|
}
|
|
615
620
|
body = {"input": texts, "model": self._config.deployment_name}
|
|
621
|
+
client = self._get_http_client()
|
|
616
622
|
last_error: Exception | None = None
|
|
617
623
|
for attempt in range(max_retries):
|
|
618
624
|
try:
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
resp.raise_for_status()
|
|
625
|
+
resp = client.post(url, headers=headers, json=body)
|
|
626
|
+
resp.raise_for_status()
|
|
622
627
|
data = resp.json()
|
|
623
628
|
results = []
|
|
624
629
|
for item in sorted(data["data"], key=lambda d: d["index"]):
|
|
@@ -20,7 +20,7 @@ from __future__ import annotations
|
|
|
20
20
|
import logging
|
|
21
21
|
from typing import Any
|
|
22
22
|
|
|
23
|
-
from superlocalmemory.core.config import SLMConfig
|
|
23
|
+
from superlocalmemory.core.config import CANONICAL_RECALL_LIMIT, SLMConfig
|
|
24
24
|
from superlocalmemory.core.engine_capabilities import Capabilities, CapabilityError
|
|
25
25
|
from superlocalmemory.core.modes import get_capabilities
|
|
26
26
|
from superlocalmemory.storage.models import (
|
|
@@ -507,7 +507,7 @@ class MemoryEngine:
|
|
|
507
507
|
|
|
508
508
|
def recall(
|
|
509
509
|
self, query: str, profile_id: str | None = None,
|
|
510
|
-
mode: Mode | None = None, limit: int =
|
|
510
|
+
mode: Mode | None = None, limit: int = CANONICAL_RECALL_LIMIT,
|
|
511
511
|
agent_id: str = "unknown",
|
|
512
512
|
session_id: str | None = None,
|
|
513
513
|
fast: bool = False,
|
|
@@ -266,7 +266,12 @@ def _remove_slm_hooks(settings: dict) -> dict:
|
|
|
266
266
|
|
|
267
267
|
|
|
268
268
|
def _read_settings() -> dict:
|
|
269
|
-
"""Read Claude Code settings.json, return empty dict if missing.
|
|
269
|
+
"""Read Claude Code settings.json, return empty dict if missing.
|
|
270
|
+
|
|
271
|
+
Raises:
|
|
272
|
+
json.JSONDecodeError: propagated as-is when the file exists but is
|
|
273
|
+
malformed, so callers can distinguish 'missing' from 'corrupt'.
|
|
274
|
+
"""
|
|
270
275
|
if CLAUDE_SETTINGS.exists():
|
|
271
276
|
return json.loads(CLAUDE_SETTINGS.read_text())
|
|
272
277
|
return {}
|
|
@@ -356,6 +361,8 @@ def check_status() -> dict:
|
|
|
356
361
|
|
|
357
362
|
hook_types_found: list[str] = []
|
|
358
363
|
has_gate = False
|
|
364
|
+
parse_error: str | None = None
|
|
365
|
+
|
|
359
366
|
try:
|
|
360
367
|
settings = _read_settings()
|
|
361
368
|
for hook_type, entries in settings.get("hooks", {}).items():
|
|
@@ -373,12 +380,26 @@ def check_status() -> dict:
|
|
|
373
380
|
break
|
|
374
381
|
if has_gate:
|
|
375
382
|
break
|
|
383
|
+
except json.JSONDecodeError as exc:
|
|
384
|
+
# File exists but is malformed — report indeterminate state so callers
|
|
385
|
+
# do not conflate 'corrupt' with 'not installed'. Claude Code reads the
|
|
386
|
+
# file independently; hooks may still be firing even though we cannot
|
|
387
|
+
# parse the file here (split-brain scenario).
|
|
388
|
+
parse_error = f"JSON parse error in settings.json: {exc}"
|
|
389
|
+
logger.warning("SLM: %s", parse_error)
|
|
376
390
|
except Exception:
|
|
377
391
|
pass
|
|
378
392
|
|
|
379
|
-
|
|
393
|
+
# Three-valued installed:
|
|
394
|
+
# True — enough SLM hook types found (≥3)
|
|
395
|
+
# False — file missing or parseable but no SLM hooks
|
|
396
|
+
# None — file exists but is corrupt (indeterminate)
|
|
397
|
+
if parse_error is not None:
|
|
398
|
+
installed: bool | None = None
|
|
399
|
+
else:
|
|
400
|
+
installed = len(hook_types_found) >= 3
|
|
380
401
|
|
|
381
|
-
|
|
402
|
+
result: dict = {
|
|
382
403
|
"installed": installed,
|
|
383
404
|
"version": installed_version,
|
|
384
405
|
"latest_version": HOOKS_VERSION,
|
|
@@ -386,6 +407,9 @@ def check_status() -> dict:
|
|
|
386
407
|
"hook_types": hook_types_found,
|
|
387
408
|
"gate_enabled": has_gate,
|
|
388
409
|
}
|
|
410
|
+
if parse_error is not None:
|
|
411
|
+
result["error"] = parse_error
|
|
412
|
+
return result
|
|
389
413
|
|
|
390
414
|
|
|
391
415
|
def upgrade_hooks() -> dict:
|