superlocalmemory 4.0.7 → 4.0.9
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/CHANGELOG.md +219 -1
- package/README.md +6 -6
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/scripts/ensure-venv.sh +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +1 -1
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +3 -3
- package/plugin-src/rules/AGENTS.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +2 -2
- package/pyproject.toml +1 -1
- package/scripts/postinstall.js +4 -0
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/_lazy_init.py +1 -1
- package/src/superlocalmemory/cli/commands.py +119 -9
- package/src/superlocalmemory/cli/db_migrate.py +0 -2
- package/src/superlocalmemory/cli/gdpr_io.py +1 -1
- package/src/superlocalmemory/cli/main.py +5 -5
- package/src/superlocalmemory/cli/service_installer.py +2 -1
- package/src/superlocalmemory/cli/setup_wizard.py +1 -1
- package/src/superlocalmemory/cli/summary_cmd.py +23 -3
- package/src/superlocalmemory/code_graph/bridge/maintenance.py +7 -1
- package/src/superlocalmemory/core/config.py +41 -7
- package/src/superlocalmemory/core/consolidation_engine.py +14 -15
- package/src/superlocalmemory/core/context_cache.py +0 -2
- package/src/superlocalmemory/core/engine.py +371 -63
- package/src/superlocalmemory/core/evidence_bundle.py +3 -1
- package/src/superlocalmemory/core/install_detector.py +131 -0
- package/src/superlocalmemory/core/progressive_abstraction.py +1 -1
- package/src/superlocalmemory/core/recall_worker.py +4 -0
- package/src/superlocalmemory/core/security_primitives.py +3 -6
- package/src/superlocalmemory/core/store_pipeline.py +94 -26
- package/src/superlocalmemory/core/topic_signature.py +0 -2
- package/src/superlocalmemory/core/transactions/concrete_owners.py +15 -8
- package/src/superlocalmemory/dynamics/eap_scheduler.py +17 -6
- package/src/superlocalmemory/encoding/graph_builder.py +2 -2
- package/src/superlocalmemory/encoding/scene_builder.py +8 -2
- package/src/superlocalmemory/evolution/skill_evolver.py +16 -1
- package/src/superlocalmemory/hooks/adapter_base.py +0 -2
- package/src/superlocalmemory/hooks/context_payload.py +0 -2
- package/src/superlocalmemory/hooks/hook_handlers.py +38 -11
- package/src/superlocalmemory/hooks/portable_kit.py +8 -8
- package/src/superlocalmemory/hooks/post_tool_async_hook.py +0 -2
- package/src/superlocalmemory/hooks/prewarm_auth.py +0 -2
- package/src/superlocalmemory/hooks/user_prompt_hook.py +0 -2
- package/src/superlocalmemory/infra/backup.py +44 -8
- package/src/superlocalmemory/integrations/bounded_loops_mcp.py +24 -7
- package/src/superlocalmemory/learning/arm_catalog.py +0 -2
- package/src/superlocalmemory/learning/bandit.py +0 -2
- package/src/superlocalmemory/learning/bandit_cache.py +0 -2
- package/src/superlocalmemory/learning/dedup_hnsw.py +11 -11
- package/src/superlocalmemory/learning/ensemble.py +0 -2
- package/src/superlocalmemory/learning/labeler.py +0 -2
- package/src/superlocalmemory/learning/legacy_migration.py +0 -2
- package/src/superlocalmemory/learning/model_cache.py +0 -2
- package/src/superlocalmemory/learning/pattern_miner.py +12 -7
- package/src/superlocalmemory/learning/ranker.py +0 -2
- package/src/superlocalmemory/learning/reward_archive.py +6 -1
- package/src/superlocalmemory/learning/reward_proxy.py +0 -2
- package/src/superlocalmemory/learning/signal_worker.py +0 -2
- package/src/superlocalmemory/math/fisher.py +1 -1
- package/src/superlocalmemory/math/hopfield.py +4 -1
- package/src/superlocalmemory/math/langevin.py +1 -1
- package/src/superlocalmemory/math/sheaf.py +7 -3
- package/src/superlocalmemory/mcp/cli_fallback.py +1 -1
- package/src/superlocalmemory/mcp/profiles.py +11 -4
- package/src/superlocalmemory/mcp/server.py +8 -1
- package/src/superlocalmemory/mcp/tools_active.py +56 -0
- package/src/superlocalmemory/mcp/tools_core.py +1 -1
- package/src/superlocalmemory/mcp/tools_summaries.py +147 -0
- package/src/superlocalmemory/optimize/cache/manager.py +2 -2
- package/src/superlocalmemory/optimize/compress/ccr.py +1 -1
- package/src/superlocalmemory/optimize/compress/router.py +1 -1
- package/src/superlocalmemory/optimize/proxy/_helpers.py +2 -2
- package/src/superlocalmemory/optimize/proxy/server.py +1 -1
- package/src/superlocalmemory/optimize/proxy/vertex_surface.py +2 -2
- package/src/superlocalmemory/optimize/storage/db.py +2 -2
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/ann_index.py +9 -2
- package/src/superlocalmemory/retrieval/bm25_channel.py +2 -2
- package/src/superlocalmemory/retrieval/bridge_discovery.py +2 -2
- package/src/superlocalmemory/retrieval/engine.py +272 -43
- package/src/superlocalmemory/retrieval/entity_channel.py +1 -1
- package/src/superlocalmemory/retrieval/hopfield_channel.py +8 -2
- package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
- package/src/superlocalmemory/retrieval/quantization_aware_search.py +1 -1
- package/src/superlocalmemory/retrieval/remote_reranker.py +2 -2
- package/src/superlocalmemory/retrieval/reranker.py +3 -3
- package/src/superlocalmemory/retrieval/semantic_channel.py +3 -3
- package/src/superlocalmemory/retrieval/spreading_activation.py +8 -8
- package/src/superlocalmemory/retrieval/strategy.py +94 -0
- package/src/superlocalmemory/retrieval/temporal_channel.py +167 -10
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +1 -1
- package/src/superlocalmemory/retrieval/vector_store.py +88 -10
- package/src/superlocalmemory/server/consolidation_runner.py +140 -0
- package/src/superlocalmemory/server/recall_serializer.py +44 -2
- package/src/superlocalmemory/server/routes/agents.py +52 -8
- package/src/superlocalmemory/server/routes/brain.py +110 -2
- package/src/superlocalmemory/server/routes/memories.py +153 -0
- package/src/superlocalmemory/server/routes/prewarm.py +4 -4
- package/src/superlocalmemory/server/routes/v3_api.py +24 -46
- package/src/superlocalmemory/server/unified_daemon.py +566 -7
- package/src/superlocalmemory/storage/_schema_version.py +46 -3
- package/src/superlocalmemory/storage/backup.py +531 -0
- package/src/superlocalmemory/storage/database.py +11 -4
- package/src/superlocalmemory/storage/embedding_codec.py +129 -0
- package/src/superlocalmemory/storage/embedding_migrator.py +5 -3
- package/src/superlocalmemory/storage/migration_runner.py +142 -2
- package/src/superlocalmemory/storage/migrations/__init__.py +1 -1
- package/src/superlocalmemory/storage/migrations.py +15 -1
- package/src/superlocalmemory/storage/models.py +7 -0
- package/src/superlocalmemory/storage/quantized_store.py +4 -2
- package/src/superlocalmemory/summaries/base.py +159 -0
- package/src/superlocalmemory/summaries/daily_reflection.py +55 -8
- package/src/superlocalmemory/summaries/project_work_log.py +23 -7
- package/src/superlocalmemory/summaries/session_summary.py +10 -6
- package/src/superlocalmemory/ui/css/legacy-dashboard.css +1 -1
- package/src/superlocalmemory/ui/css/neural-glass.css +1 -1
- package/src/superlocalmemory/ui/index.html +9 -3
- package/src/superlocalmemory/ui/js/core.js +1 -1
- package/src/superlocalmemory/ui/js/od-boundedloops.js +324 -0
- package/src/superlocalmemory/ui/js/od-brain.js +1 -1
- package/src/superlocalmemory/ui/js/od-memories.js +337 -12
- package/src/superlocalmemory/ui/js/od-mesh.js +97 -5
- package/src/superlocalmemory/ui/js/od-operations.js +1 -150
- package/src/superlocalmemory/ui/js/od-optimize.js +36 -9
- package/src/superlocalmemory/ui/js/od-shell.js +10 -0
|
@@ -1147,7 +1147,7 @@ def _cmd_context_dispatch(args: Namespace) -> None:
|
|
|
1147
1147
|
|
|
1148
1148
|
|
|
1149
1149
|
def _agents_md_source_factory():
|
|
1150
|
-
"""Return a callable that reads the
|
|
1150
|
+
"""Return a callable that reads the bundled AGENTS.md content, or None on failure.
|
|
1151
1151
|
|
|
1152
1152
|
Source: plugin-src/rules/AGENTS.md (relative to package root).
|
|
1153
1153
|
Gracefully skips if absent — never fails the MCP write.
|
|
@@ -1168,7 +1168,7 @@ def _agents_md_source_factory():
|
|
|
1168
1168
|
if candidate.exists():
|
|
1169
1169
|
return candidate.read_text(encoding="utf-8")
|
|
1170
1170
|
logger.warning(
|
|
1171
|
-
"
|
|
1171
|
+
"AGENTS.md is not bundled in this distribution — skipping AGENTS.md write"
|
|
1172
1172
|
)
|
|
1173
1173
|
return None
|
|
1174
1174
|
|
|
@@ -1178,15 +1178,15 @@ def _agents_md_source_factory():
|
|
|
1178
1178
|
def cmd_connect(args: Namespace) -> None:
|
|
1179
1179
|
"""Configure IDE integrations.
|
|
1180
1180
|
|
|
1181
|
-
Dispatch priority
|
|
1181
|
+
Dispatch priority:
|
|
1182
1182
|
1. ``slm connect <ide>`` where ide ∈ IDE_MATRIX → portable_kit.connect_ide
|
|
1183
|
-
(MCP-wiring + AGENTS.md;
|
|
1183
|
+
(MCP-wiring + AGENTS.md; claude-code short-circuits to the plugin pointer).
|
|
1184
1184
|
2. ``--cross-platform`` / ``--disable`` → LLD-05 CrossPlatformConnector.
|
|
1185
1185
|
3. Bare ``slm connect`` / ``--list`` → legacy IDEConnector (markdown-rules).
|
|
1186
1186
|
"""
|
|
1187
1187
|
ide_arg = getattr(args, "ide", None)
|
|
1188
1188
|
|
|
1189
|
-
#
|
|
1189
|
+
# Intercept known IDE_MATRIX ids before legacy branches (critical: must run first)
|
|
1190
1190
|
if ide_arg is not None:
|
|
1191
1191
|
from superlocalmemory.hooks.portable_kit import (
|
|
1192
1192
|
IDE_MATRIX,
|
|
@@ -1922,7 +1922,7 @@ def cmd_status(args: Namespace) -> None:
|
|
|
1922
1922
|
])
|
|
1923
1923
|
return
|
|
1924
1924
|
|
|
1925
|
-
#
|
|
1925
|
+
# canonical key set — db_size_mb always present (0.0 if absent).
|
|
1926
1926
|
db_size_mb = 0.0
|
|
1927
1927
|
if config.db_path.exists():
|
|
1928
1928
|
db_size_mb = round(config.db_path.stat().st_size / 1024 / 1024, 2)
|
|
@@ -2376,6 +2376,52 @@ def cmd_help(args: Namespace) -> None:
|
|
|
2376
2376
|
print("Docs: https://superlocalmemory.com")
|
|
2377
2377
|
|
|
2378
2378
|
|
|
2379
|
+
def _migration_error_logs() -> list:
|
|
2380
|
+
"""Return a sorted list of migration-error log files in the SLM home dir.
|
|
2381
|
+
|
|
2382
|
+
Returns an empty list when none exist or when the directory is not readable.
|
|
2383
|
+
"""
|
|
2384
|
+
try:
|
|
2385
|
+
from superlocalmemory.core.config import SLMConfig
|
|
2386
|
+
from superlocalmemory.infra.data_root import canonical_data_root
|
|
2387
|
+
from superlocalmemory.storage.models import Mode
|
|
2388
|
+
|
|
2389
|
+
# The daemon writes this log beside the memory database it was actually
|
|
2390
|
+
# configured with, which is not necessarily the canonical data root —
|
|
2391
|
+
# any of SLM_DATA_DIR / SL_MEMORY_PATH / SLM_HOME can move it. Globbing
|
|
2392
|
+
# only the canonical root reported "clean" while the log sat unread in
|
|
2393
|
+
# the real directory. Search both, de-duplicated.
|
|
2394
|
+
roots = {canonical_data_root()}
|
|
2395
|
+
try:
|
|
2396
|
+
cfg = SLMConfig.for_mode(Mode.A)
|
|
2397
|
+
db = getattr(cfg, "memory_db_path", None) or getattr(cfg, "db_path", None)
|
|
2398
|
+
if db:
|
|
2399
|
+
roots.add(pathlib.Path(db).parent)
|
|
2400
|
+
except Exception:
|
|
2401
|
+
pass
|
|
2402
|
+
|
|
2403
|
+
found: list = []
|
|
2404
|
+
for root in roots:
|
|
2405
|
+
try:
|
|
2406
|
+
found.extend(root.glob("migration-error-*.log"))
|
|
2407
|
+
except OSError:
|
|
2408
|
+
continue
|
|
2409
|
+
return sorted(set(found))
|
|
2410
|
+
except Exception:
|
|
2411
|
+
return []
|
|
2412
|
+
|
|
2413
|
+
|
|
2414
|
+
def _detect_all_installs() -> list:
|
|
2415
|
+
"""Thin shim so cmd_doctor can be tested without importing install_detector."""
|
|
2416
|
+
try:
|
|
2417
|
+
from superlocalmemory.core.install_detector import (
|
|
2418
|
+
_detect_all_installs as _real,
|
|
2419
|
+
)
|
|
2420
|
+
return _real()
|
|
2421
|
+
except Exception:
|
|
2422
|
+
return []
|
|
2423
|
+
|
|
2424
|
+
|
|
2379
2425
|
def cmd_doctor(args: Namespace) -> None:
|
|
2380
2426
|
"""Comprehensive pre-flight check — verify everything works.
|
|
2381
2427
|
|
|
@@ -2728,7 +2774,7 @@ def cmd_doctor(args: Namespace) -> None:
|
|
|
2728
2774
|
else:
|
|
2729
2775
|
_check("Database", "PASS", "not yet created (will initialize on first use)")
|
|
2730
2776
|
|
|
2731
|
-
# 11. PEP 668 advisory
|
|
2777
|
+
# 11. PEP 668 advisory: detect EXTERNALLY-MANAGED marker and
|
|
2732
2778
|
# recommend pipx when the system Python is managed by the OS package
|
|
2733
2779
|
# manager (e.g. Homebrew, Debian/Ubuntu, Fedora 38+).
|
|
2734
2780
|
try:
|
|
@@ -2782,6 +2828,70 @@ def cmd_doctor(args: Namespace) -> None:
|
|
|
2782
2828
|
except Exception as _cr_exc:
|
|
2783
2829
|
_check("Component registry", "WARN", f"probe failed: {_cr_exc}")
|
|
2784
2830
|
|
|
2831
|
+
# 13. Install version convergence — detect all copies of SLM on this machine
|
|
2832
|
+
# and warn when they are at different versions. Silent divergence is the
|
|
2833
|
+
# most common source of unexpected behaviour after an upgrade.
|
|
2834
|
+
try:
|
|
2835
|
+
_installs = _detect_all_installs() # shim defined above cmd_doctor
|
|
2836
|
+
_versions = {i["version"] for i in _installs}
|
|
2837
|
+
if not _installs:
|
|
2838
|
+
_check(
|
|
2839
|
+
"install_versions",
|
|
2840
|
+
"PASS",
|
|
2841
|
+
"No additional SLM installs detected on this machine",
|
|
2842
|
+
)
|
|
2843
|
+
elif len(_versions) > 1:
|
|
2844
|
+
_detail = "Version divergence: " + ", ".join(
|
|
2845
|
+
f"{i['type']}={i['version']}" for i in _installs
|
|
2846
|
+
)
|
|
2847
|
+
_check(
|
|
2848
|
+
"install_versions",
|
|
2849
|
+
"WARN",
|
|
2850
|
+
_detail,
|
|
2851
|
+
fix="Upgrade all installs: "
|
|
2852
|
+
"pipx upgrade superlocalmemory | "
|
|
2853
|
+
"pip install -U superlocalmemory (in ~/.slm-venv) | "
|
|
2854
|
+
"npm install -g superlocalmemory@latest",
|
|
2855
|
+
)
|
|
2856
|
+
else:
|
|
2857
|
+
_unified = next(iter(_versions))
|
|
2858
|
+
_types = ", ".join(i["type"] for i in _installs)
|
|
2859
|
+
_check(
|
|
2860
|
+
"install_versions",
|
|
2861
|
+
"PASS",
|
|
2862
|
+
f"All installs at {_unified} ({_types})",
|
|
2863
|
+
)
|
|
2864
|
+
except Exception as _inst_exc: # noqa: BLE001 — never break doctor
|
|
2865
|
+
_check("install_versions", "WARN", f"could not probe installs: {_inst_exc}")
|
|
2866
|
+
|
|
2867
|
+
# 14. Migration error logs — surface any unresolved failure from a previous
|
|
2868
|
+
# upgrade attempt. The daemon writes these and they persist until the
|
|
2869
|
+
# user takes action; doctor is the right place to surface them.
|
|
2870
|
+
try:
|
|
2871
|
+
_err_logs = _migration_error_logs()
|
|
2872
|
+
if _err_logs:
|
|
2873
|
+
_latest = _err_logs[-1]
|
|
2874
|
+
_check(
|
|
2875
|
+
"migration_errors",
|
|
2876
|
+
"FAIL",
|
|
2877
|
+
f"Unresolved migration failure: {_latest.name}",
|
|
2878
|
+
# `--fix` heals models, sqlite-vec and stale locks. It does NOT
|
|
2879
|
+
# re-run a migration or restore a snapshot, so pointing here at
|
|
2880
|
+
# `--fix` sent the user round a loop that could never clear this
|
|
2881
|
+
# check. The log itself carries the restore command.
|
|
2882
|
+
fix=(
|
|
2883
|
+
f"Read {_latest} — it names what failed and, if a snapshot "
|
|
2884
|
+
f"was taken, the exact command to restore it. Restart the "
|
|
2885
|
+
f"daemon to retry the upgrade. Delete the log once resolved."
|
|
2886
|
+
),
|
|
2887
|
+
)
|
|
2888
|
+
else:
|
|
2889
|
+
# Reporting nothing when clean made "no failures" and "the check
|
|
2890
|
+
# never ran" look identical in the output.
|
|
2891
|
+
_check("migration_errors", "PASS", "no unresolved migration failures")
|
|
2892
|
+
except Exception as _mel_exc: # noqa: BLE001 — never break doctor
|
|
2893
|
+
_check("migration_errors", "WARN", f"could not check error logs: {_mel_exc}")
|
|
2894
|
+
|
|
2785
2895
|
# 12. Optimize (Surface B) — reads daemon-persisted metrics (≤60s stale).
|
|
2786
2896
|
info = _gather_optimize_surface_b()
|
|
2787
2897
|
_enabled = info["enabled"]
|
|
@@ -3332,7 +3442,7 @@ def _cmd_init_auto(
|
|
|
3332
3442
|
config_exists: bool,
|
|
3333
3443
|
force: bool,
|
|
3334
3444
|
) -> None:
|
|
3335
|
-
"""
|
|
3445
|
+
"""Non-interactive --auto branch for slm init.
|
|
3336
3446
|
|
|
3337
3447
|
Best-effort at every step; only exits non-zero when config save fails.
|
|
3338
3448
|
No TTY required. Does NOT run IDE connect (AC6).
|
|
@@ -3394,7 +3504,7 @@ def cmd_init(args: Namespace) -> None:
|
|
|
3394
3504
|
slm_data_dir = slm_home()
|
|
3395
3505
|
config_exists = (slm_data_dir / "config.json").exists()
|
|
3396
3506
|
|
|
3397
|
-
#
|
|
3507
|
+
# --auto branch — fully non-interactive, no TTY required.
|
|
3398
3508
|
if auto:
|
|
3399
3509
|
os.environ["SLM_NON_INTERACTIVE"] = "1"
|
|
3400
3510
|
_cmd_init_auto(args, slm_data_dir, config_exists, force)
|
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
"""CLI handler for ``slm db migrate``.
|
|
6
6
|
|
|
7
|
-
LLD reference: ``.backup/active-brain/lld/LLD-06-windows-binary-and-legacy-migration.md``
|
|
8
|
-
Section 7.2 (slm db migrate CLI).
|
|
9
7
|
|
|
10
8
|
Thin wrapper over the canonical runner in
|
|
11
9
|
``superlocalmemory.storage.migration_runner``. This module owns only
|
|
@@ -73,7 +73,7 @@ def _audit_chain_path() -> Path:
|
|
|
73
73
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
74
74
|
|
|
75
75
|
# Known gaps in this release — reported honestly so a DPO can plan remediation.
|
|
76
|
-
# Gaps C1 and C2
|
|
76
|
+
# Gaps C1 and C2 (cross-device erase verification and audit-log replay) remain open.
|
|
77
77
|
KNOWN_GAPS = [
|
|
78
78
|
{
|
|
79
79
|
"ref": "C1",
|
|
@@ -134,7 +134,7 @@ def main() -> None:
|
|
|
134
134
|
and "--apply" not in sys.argv[2:]
|
|
135
135
|
)
|
|
136
136
|
|
|
137
|
-
#
|
|
137
|
+
# lazy first-run init — runs after hook/mcp fast-paths so stdout
|
|
138
138
|
# is never polluted on those paths (CRIT-3, MCP JSON-RPC purity).
|
|
139
139
|
# Guarded: any failure must not crash the CLI (AC4).
|
|
140
140
|
_is_mcp_cmd = len(sys.argv) >= 2 and sys.argv[1] == "mcp"
|
|
@@ -169,7 +169,7 @@ def main() -> None:
|
|
|
169
169
|
from superlocalmemory.migrations.v3_4_25_to_v3_4_26 import (
|
|
170
170
|
migrate_if_safe as _migrate_if_safe,
|
|
171
171
|
)
|
|
172
|
-
#
|
|
172
|
+
# route through slm_home() so all 3 env aliases are honoured.
|
|
173
173
|
from superlocalmemory.cli._lazy_init import slm_home as _slm_home
|
|
174
174
|
_data = _slm_home()
|
|
175
175
|
_res = _migrate_if_safe(_data)
|
|
@@ -209,7 +209,7 @@ def main() -> None:
|
|
|
209
209
|
"--gate", action="store_true",
|
|
210
210
|
help="Enable PreToolUse gate (experimental — blocks tools until session_init)",
|
|
211
211
|
)
|
|
212
|
-
#
|
|
212
|
+
# non-interactive auto setup (pip post-install, CI, scripts).
|
|
213
213
|
init_p.add_argument(
|
|
214
214
|
"--auto", action="store_true",
|
|
215
215
|
help="Non-interactive setup: mode A + hooks (no TTY required, for CI/scripts)",
|
|
@@ -243,7 +243,7 @@ def main() -> None:
|
|
|
243
243
|
"--list", action="store_true", help="List all supported IDEs",
|
|
244
244
|
)
|
|
245
245
|
connect_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
246
|
-
#
|
|
246
|
+
# declare missing flags so cmd_connect can read them without getattr fallback
|
|
247
247
|
connect_p.add_argument(
|
|
248
248
|
"--here", action="store_true", default=False,
|
|
249
249
|
help="Write config relative to current working directory (project scope)",
|
|
@@ -258,7 +258,7 @@ def main() -> None:
|
|
|
258
258
|
)
|
|
259
259
|
connect_p.add_argument(
|
|
260
260
|
"--profile", metavar="PROFILE", default=None,
|
|
261
|
-
help="Inject SLM_MCP_PROFILE env var into the MCP server block (
|
|
261
|
+
help="Inject SLM_MCP_PROFILE env var into the MCP server block (named profile selection)",
|
|
262
262
|
)
|
|
263
263
|
connect_p.add_argument(
|
|
264
264
|
"--dry-run", action="store_true", dest="dry_run", default=False,
|
|
@@ -19,7 +19,6 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import logging
|
|
21
21
|
import os
|
|
22
|
-
import plistlib
|
|
23
22
|
import subprocess
|
|
24
23
|
import sys
|
|
25
24
|
from pathlib import Path
|
|
@@ -56,6 +55,8 @@ def _macos_plist_path() -> Path:
|
|
|
56
55
|
|
|
57
56
|
|
|
58
57
|
def _macos_plist_content() -> str:
|
|
58
|
+
import plistlib # macOS-only stdlib module; deferred so non-macOS platforms can import this module safely
|
|
59
|
+
|
|
59
60
|
python = get_python_path()
|
|
60
61
|
log = get_log_path()
|
|
61
62
|
err_log = get_error_log_path()
|
|
@@ -29,7 +29,7 @@ from typing import Any
|
|
|
29
29
|
# Constants
|
|
30
30
|
# ---------------------------------------------------------------------------
|
|
31
31
|
|
|
32
|
-
#
|
|
32
|
+
# resolve via slm_home() so all 3 env aliases are honoured.
|
|
33
33
|
# Fallback keeps stdlib-only path if the import fails during early bootstrap.
|
|
34
34
|
def _resolve_slm_home() -> Path:
|
|
35
35
|
from superlocalmemory.infra.data_root import canonical_data_root
|
|
@@ -86,6 +86,25 @@ def _active_profile() -> str:
|
|
|
86
86
|
return "default"
|
|
87
87
|
|
|
88
88
|
|
|
89
|
+
def _load_config() -> Any:
|
|
90
|
+
"""The active SLMConfig, so Mode B/C get LLM-written summaries.
|
|
91
|
+
|
|
92
|
+
Without this the generators receive ``config=None``, ``get_mode_str`` returns
|
|
93
|
+
``"a"``, and the LLM branch is unreachable — every summary comes out
|
|
94
|
+
extractive no matter which mode the user runs. That is not a graceful
|
|
95
|
+
fallback, it is the enrichment path never being offered.
|
|
96
|
+
|
|
97
|
+
Returns None on any failure, which lands on the extractive path. That is the
|
|
98
|
+
right fallback: a deterministic summary beats an error.
|
|
99
|
+
"""
|
|
100
|
+
try:
|
|
101
|
+
from superlocalmemory.core.config import SLMConfig
|
|
102
|
+
|
|
103
|
+
return SLMConfig.load()
|
|
104
|
+
except Exception:
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
|
|
89
108
|
def _emit(result: Any, as_json: bool) -> None:
|
|
90
109
|
"""Print a SummaryResult as JSON or as prose."""
|
|
91
110
|
if as_json:
|
|
@@ -123,6 +142,7 @@ def cmd_summary(args: Namespace) -> None:
|
|
|
123
142
|
as_json = bool(getattr(args, "json", False))
|
|
124
143
|
profile = getattr(args, "profile", None) or _active_profile()
|
|
125
144
|
db = _db_path()
|
|
145
|
+
cfg = _load_config() # Mode B/C enrichment; None -> extractive
|
|
126
146
|
|
|
127
147
|
if not db.exists():
|
|
128
148
|
print(f"No memory database at {db}. Run `slm status` first.")
|
|
@@ -131,7 +151,7 @@ def cmd_summary(args: Namespace) -> None:
|
|
|
131
151
|
if sub == "session":
|
|
132
152
|
from superlocalmemory.summaries import generate_session_summary
|
|
133
153
|
|
|
134
|
-
_emit(generate_session_summary(db, args.session_id, profile), as_json)
|
|
154
|
+
_emit(generate_session_summary(db, args.session_id, profile, cfg), as_json)
|
|
135
155
|
return
|
|
136
156
|
|
|
137
157
|
if sub == "day":
|
|
@@ -142,14 +162,14 @@ def cmd_summary(args: Namespace) -> None:
|
|
|
142
162
|
target = (date.today() - timedelta(days=1)).isoformat()
|
|
143
163
|
elif target == "today":
|
|
144
164
|
target = date.today().isoformat()
|
|
145
|
-
_emit(generate_daily_reflection(db, target, profile), as_json)
|
|
165
|
+
_emit(generate_daily_reflection(db, target, profile, cfg), as_json)
|
|
146
166
|
return
|
|
147
167
|
|
|
148
168
|
if sub == "project":
|
|
149
169
|
from superlocalmemory.summaries import generate_project_work_log
|
|
150
170
|
|
|
151
171
|
path = getattr(args, "path", None) or str(Path.cwd())
|
|
152
|
-
_emit(generate_project_work_log(db, path, profile), as_json)
|
|
172
|
+
_emit(generate_project_work_log(db, path, profile, cfg), as_json)
|
|
153
173
|
return
|
|
154
174
|
|
|
155
175
|
print("Usage: slm summary {session <id> | day [DATE] | project [PATH]}")
|
|
@@ -96,7 +96,13 @@ def _fact_rows(
|
|
|
96
96
|
except Exception: # pragma: no cover - helper absent on an unusual manager
|
|
97
97
|
has_archive = False
|
|
98
98
|
if has_archive:
|
|
99
|
-
|
|
99
|
+
# COALESCE form, matching storage/database.py:759 and the learning
|
|
100
|
+
# modules. My first version tested `IS NULL OR = ''`, which excluded
|
|
101
|
+
# every fact on a real store: live facts are marked 'live', not blank.
|
|
102
|
+
# The synthetic fixtures I developed against left the column unset, so
|
|
103
|
+
# the pass linked happily in tests and would have linked NOTHING for any
|
|
104
|
+
# actual user — 3,608 of 3,608 facts filtered out on this machine.
|
|
105
|
+
sql += "AND COALESCE(archive_status, 'live') != 'archived' "
|
|
100
106
|
|
|
101
107
|
params: list[Any] = [profile_id]
|
|
102
108
|
if since:
|
|
@@ -23,7 +23,7 @@ logger = logging.getLogger(__name__)
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
# ---------------------------------------------------------------------------
|
|
26
|
-
# Canonical limits
|
|
26
|
+
# Canonical limits — single source of truth across all surfaces
|
|
27
27
|
# ---------------------------------------------------------------------------
|
|
28
28
|
|
|
29
29
|
#: Default number of results returned by recall across MCP, CLI, daemon, and
|
|
@@ -387,6 +387,21 @@ class RetrievalConfig:
|
|
|
387
387
|
# fail-open — never a per-query LLM call. Kill-switch for tuning.
|
|
388
388
|
enable_community_context: bool = True
|
|
389
389
|
|
|
390
|
+
# Recency query strategy.
|
|
391
|
+
# When False, queries matching present-activity phrases fall through to
|
|
392
|
+
# factual routing — one-line rollback without touching strategy.py.
|
|
393
|
+
enable_recency_strategy: bool = True
|
|
394
|
+
# Strength of the recency prior applied during final-score fusion.
|
|
395
|
+
# 0.0 = no recency bias (restores previous ranking exactly).
|
|
396
|
+
# Consumed by a separate ranking stage; this field makes the knob
|
|
397
|
+
# configurable without further edits to this class.
|
|
398
|
+
recency_prior_strength: float = 0.5
|
|
399
|
+
# A fact written moments ago has no embedding yet, so it scores zero on the
|
|
400
|
+
# semantic channel — the same score a genuinely unrelated fact gets. Inside
|
|
401
|
+
# this window that zero is read as "not computed yet", not as "unrelated".
|
|
402
|
+
write_recency_floor_enabled: bool = True
|
|
403
|
+
write_recency_floor_minutes: float = 60.0
|
|
404
|
+
|
|
390
405
|
|
|
391
406
|
# ---------------------------------------------------------------------------
|
|
392
407
|
# Math Config
|
|
@@ -564,7 +579,26 @@ class HopfieldConfig:
|
|
|
564
579
|
max_iterations: int = 1
|
|
565
580
|
convergence_epsilon: float = 1e-6
|
|
566
581
|
prefilter_threshold: int = 10_000
|
|
567
|
-
|
|
582
|
+
# How many candidates the index hands to this stage. This stage decides final
|
|
583
|
+
# membership, so a fact ranked past this number by the index never reaches it
|
|
584
|
+
# and can never be returned — the size of this pool is a direct limit on which
|
|
585
|
+
# memories are reachable at all.
|
|
586
|
+
#
|
|
587
|
+
#
|
|
588
|
+
# Chosen by measurement against the 2 s recall ceiling, on a 517 MB store:
|
|
589
|
+
# 150 -> p95 998 ms, 0 channel timeouts
|
|
590
|
+
# 300 -> p95 1054 ms, 0
|
|
591
|
+
# 500 -> p95 1140 ms, 0 <- here: 3.3x the reachability of 150 for +142 ms
|
|
592
|
+
# 750 -> p95 1278 ms, 0
|
|
593
|
+
# 1000 -> p95 1947-2737 ms, 2-25 timeouts
|
|
594
|
+
#
|
|
595
|
+
# 1000 is excluded twice over: it breaches the ceiling, and a channel that
|
|
596
|
+
# misses its deadline contributes nothing to fusion — so whether it lands
|
|
597
|
+
# varies with machine load and the same question stops returning the same
|
|
598
|
+
# answer. 500 keeps ~860 ms of headroom, which is several times the observed
|
|
599
|
+
# run-to-run variance, and stays clear of the region where the cost curve
|
|
600
|
+
# turned non-linear for reasons that were never explained.
|
|
601
|
+
prefilter_candidates: int = 500
|
|
568
602
|
skip_threshold: int = 100_000
|
|
569
603
|
cache_ttl_seconds: float = 60.0
|
|
570
604
|
|
|
@@ -1727,7 +1761,7 @@ class SLMConfig:
|
|
|
1727
1761
|
embedding_dimension: int = 0,
|
|
1728
1762
|
) -> SLMConfig:
|
|
1729
1763
|
"""Create config with mode-appropriate defaults."""
|
|
1730
|
-
#
|
|
1764
|
+
# resolve base dir via slm_home() at call time when base_dir is not explicit.
|
|
1731
1765
|
if base_dir is None:
|
|
1732
1766
|
try:
|
|
1733
1767
|
from superlocalmemory.cli._lazy_init import slm_home as _slm_home
|
|
@@ -1895,7 +1929,7 @@ class SLMConfig:
|
|
|
1895
1929
|
|
|
1896
1930
|
Returns ``\"b\"`` (the default) if the file doesn't exist.
|
|
1897
1931
|
"""
|
|
1898
|
-
#
|
|
1932
|
+
# resolve via slm_home() at call time when base_dir is not explicit.
|
|
1899
1933
|
if base_dir is None:
|
|
1900
1934
|
try:
|
|
1901
1935
|
from superlocalmemory.cli._lazy_init import slm_home as _slm_home
|
|
@@ -1913,7 +1947,7 @@ class SLMConfig:
|
|
|
1913
1947
|
@staticmethod
|
|
1914
1948
|
def write_current_mode(mode: str, base_dir: Path | None = None) -> None:
|
|
1915
1949
|
"""Write the current mode letter to ``current_mode``."""
|
|
1916
|
-
#
|
|
1950
|
+
# resolve via slm_home() at call time when base_dir is not explicit.
|
|
1917
1951
|
if base_dir is None:
|
|
1918
1952
|
try:
|
|
1919
1953
|
from superlocalmemory.cli._lazy_init import slm_home as _slm_home
|
|
@@ -1956,7 +1990,7 @@ class SLMConfig:
|
|
|
1956
1990
|
import dataclasses
|
|
1957
1991
|
|
|
1958
1992
|
from superlocalmemory.storage.models import Mode as _M
|
|
1959
|
-
#
|
|
1993
|
+
# resolve via slm_home() at call time when base_dir is not explicit.
|
|
1960
1994
|
if base_dir is None:
|
|
1961
1995
|
try:
|
|
1962
1996
|
from superlocalmemory.cli._lazy_init import slm_home as _slm_home
|
|
@@ -2031,7 +2065,7 @@ class SLMConfig:
|
|
|
2031
2065
|
Called on daemon boot. Idempotent — if ``current_mode`` already
|
|
2032
2066
|
exists, this is a no-op. Returns True if migration was performed.
|
|
2033
2067
|
"""
|
|
2034
|
-
#
|
|
2068
|
+
# resolve via slm_home() at call time when base_dir is not explicit.
|
|
2035
2069
|
if base_dir is None:
|
|
2036
2070
|
try:
|
|
2037
2071
|
from superlocalmemory.cli._lazy_init import slm_home as _slm_home
|
|
@@ -198,27 +198,26 @@ class ConsolidationEngine:
|
|
|
198
198
|
from superlocalmemory.parameterization.prompt_injector import PromptInjector
|
|
199
199
|
from superlocalmemory.parameterization.prompt_lifecycle import PromptLifecycleManager
|
|
200
200
|
from superlocalmemory.learning.behavioral import BehavioralPatternStore
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
from superlocalmemory.parameterization.cross_project import CrossProjectAggregator
|
|
204
|
-
except ImportError:
|
|
205
|
-
class CrossProjectAggregator:
|
|
206
|
-
def __init__(self, db): pass
|
|
207
|
-
def get_preferences(self, *a, **kw): return {}
|
|
208
|
-
try:
|
|
209
|
-
from superlocalmemory.parameterization.workflow_miner import WorkflowMiner
|
|
210
|
-
except ImportError:
|
|
211
|
-
class WorkflowMiner:
|
|
212
|
-
def __init__(self, db): pass
|
|
213
|
-
def mine(self, *a, **kw): return []
|
|
201
|
+
from superlocalmemory.parameterization.cross_project import CrossProjectAggregator
|
|
202
|
+
from superlocalmemory.parameterization.workflow_miner import WorkflowMiner
|
|
214
203
|
from superlocalmemory.hooks.auto_parameterize import AutoParameterizeHook
|
|
215
204
|
from superlocalmemory.core.config import ParameterizationConfig
|
|
216
205
|
p_config = getattr(self._config, "parameterization", ParameterizationConfig())
|
|
217
206
|
from superlocalmemory.infra.data_root import state_path
|
|
218
207
|
learning_db = str(state_path("learning.db"))
|
|
219
208
|
beh_store = BehavioralPatternStore(learning_db)
|
|
220
|
-
|
|
221
|
-
|
|
209
|
+
# Both take a DB PATH, not a DatabaseManager. Passing
|
|
210
|
+
# self._db here made Path(db_path) raise "argument should be
|
|
211
|
+
# a str or an os.PathLike object ... not 'DatabaseManager'",
|
|
212
|
+
# which step 9 caught at debug level — so soft prompts were
|
|
213
|
+
# never generated and nothing said why.
|
|
214
|
+
#
|
|
215
|
+
# The ImportError stubs these replaced were also dead: both
|
|
216
|
+
# modules exist and re-export the canonical classes, so the
|
|
217
|
+
# stub branch could never run and only served to hide the
|
|
218
|
+
# real signatures.
|
|
219
|
+
cross_proj = CrossProjectAggregator(learning_db)
|
|
220
|
+
wf_miner = WorkflowMiner(learning_db)
|
|
222
221
|
extractor = PatternExtractor(self._db, beh_store, cross_proj, wf_miner, p_config)
|
|
223
222
|
generator = SoftPromptGenerator(p_config)
|
|
224
223
|
injector = PromptInjector(self._db, generator, p_config)
|
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
"""Context cache — separate SQLite WAL DB, read-path <10 ms.
|
|
6
6
|
|
|
7
|
-
LLD reference: `.backup/active-brain/lld/LLD-01-context-cache-and-hot-path-hooks.md`
|
|
8
|
-
Section 4.1.
|
|
9
7
|
|
|
10
8
|
Two concerns in one module:
|
|
11
9
|
- Writer (``ContextCache``) — used by the daemon only. Owns pragmas,
|