superlocalmemory 4.0.4 → 4.0.6

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 (90) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +23 -14
  3. package/ide/configs/codex-mcp.toml +2 -2
  4. package/package.json +3 -1
  5. package/plugin/.claude-plugin/plugin.json +1 -1
  6. package/plugin/.mcp.json +1 -0
  7. package/plugin/CLAUDE.md +3 -3
  8. package/plugin/agents/slm-governance-advisor.md +1 -1
  9. package/plugin/agents/slm-loop-runner.md +1 -1
  10. package/plugin/agents/slm-memory-advisor.md +1 -1
  11. package/plugin/agents/slm-optimize-advisor.md +1 -1
  12. package/plugin/requirements.txt +1 -1
  13. package/plugin/skills/slm-cache/SKILL.md +1 -1
  14. package/plugin/skills/slm-compress/SKILL.md +1 -1
  15. package/plugin/skills/slm-governance/SKILL.md +1 -1
  16. package/plugin/skills/slm-graph/SKILL.md +3 -2
  17. package/plugin/skills/slm-loop/SKILL.md +1 -1
  18. package/plugin/skills/slm-mesh/SKILL.md +1 -1
  19. package/plugin/skills/slm-profile/SKILL.md +2 -1
  20. package/plugin/skills/slm-recall/SKILL.md +1 -1
  21. package/plugin/skills/slm-remember/SKILL.md +1 -1
  22. package/plugin/skills/slm-scope/SKILL.md +1 -1
  23. package/plugin/skills/slm-session/SKILL.md +1 -1
  24. package/plugin/skills/slm-status/SKILL.md +1 -1
  25. package/plugin-src/rules/AGENTS.md +6 -5
  26. package/plugin-src/skills/slm-graph/SKILL.md +2 -1
  27. package/plugin-src/skills/slm-profile/SKILL.md +1 -0
  28. package/pyproject.toml +1 -1
  29. package/src/superlocalmemory/__init__.py +1 -1
  30. package/src/superlocalmemory/access/rbac.py +106 -0
  31. package/src/superlocalmemory/brain/__init__.py +5 -0
  32. package/src/superlocalmemory/brain/truth.py +418 -0
  33. package/src/superlocalmemory/cli/__main__.py +17 -0
  34. package/src/superlocalmemory/cli/commands.py +96 -28
  35. package/src/superlocalmemory/cli/gdpr_cmd.py +779 -0
  36. package/src/superlocalmemory/cli/gdpr_io.py +109 -0
  37. package/src/superlocalmemory/cli/main.py +88 -0
  38. package/src/superlocalmemory/code_graph/extractors/__init__.py +17 -0
  39. package/src/superlocalmemory/code_graph/graph_store.py +180 -3
  40. package/src/superlocalmemory/code_graph/parser.py +280 -100
  41. package/src/superlocalmemory/compliance/gdpr.py +358 -0
  42. package/src/superlocalmemory/core/config.py +44 -1
  43. package/src/superlocalmemory/core/context_cache.py +58 -1
  44. package/src/superlocalmemory/core/engine_wiring.py +5 -1
  45. package/src/superlocalmemory/core/maintenance.py +43 -1
  46. package/src/superlocalmemory/core/mutations.py +155 -25
  47. package/src/superlocalmemory/core/recall_pipeline.py +6 -10
  48. package/src/superlocalmemory/core/recall_worker.py +33 -12
  49. package/src/superlocalmemory/core/remember_runtime.py +271 -2
  50. package/src/superlocalmemory/core/store_pipeline.py +100 -38
  51. package/src/superlocalmemory/encoding/consolidator.py +17 -47
  52. package/src/superlocalmemory/encoding/temporal_validator.py +14 -18
  53. package/src/superlocalmemory/hooks/user_prompt_hook.py +1 -1
  54. package/src/superlocalmemory/infra/backup.py +138 -0
  55. package/src/superlocalmemory/infra/backup_obligations.py +423 -0
  56. package/src/superlocalmemory/integrations/bounded_loops_mcp.py +4 -3
  57. package/src/superlocalmemory/learning/engagement.py +165 -0
  58. package/src/superlocalmemory/mcp/profiles.py +19 -7
  59. package/src/superlocalmemory/mcp/server.py +4 -2
  60. package/src/superlocalmemory/mcp/tools_brain.py +54 -10
  61. package/src/superlocalmemory/mcp/tools_code_graph.py +31 -4
  62. package/src/superlocalmemory/mcp/tools_core.py +88 -3
  63. package/src/superlocalmemory/mcp/tools_v3.py +20 -6
  64. package/src/superlocalmemory/retrieval/engine.py +28 -10
  65. package/src/superlocalmemory/retrieval/remote_reranker.py +108 -11
  66. package/src/superlocalmemory/retrieval/temporal_validity_filter.py +119 -19
  67. package/src/superlocalmemory/server/routes/brain.py +297 -14
  68. package/src/superlocalmemory/server/routes/learning.py +13 -25
  69. package/src/superlocalmemory/server/routes/memories.py +129 -3
  70. package/src/superlocalmemory/server/routes/v3_api.py +171 -60
  71. package/src/superlocalmemory/storage/_migration_internals.py +4 -0
  72. package/src/superlocalmemory/storage/_schema_version.py +2 -2
  73. package/src/superlocalmemory/storage/correction_cases.py +670 -0
  74. package/src/superlocalmemory/storage/database.py +230 -24
  75. package/src/superlocalmemory/storage/migration_runner.py +7 -0
  76. package/src/superlocalmemory/storage/migrations/M042_correction_case_ledger.py +245 -0
  77. package/src/superlocalmemory/storage/migrations/__init__.py +2 -0
  78. package/src/superlocalmemory/storage/models.py +12 -4
  79. package/src/superlocalmemory/storage/write_coordinator.py +4 -0
  80. package/src/superlocalmemory/summaries/__init__.py +37 -0
  81. package/src/superlocalmemory/summaries/base.py +108 -0
  82. package/src/superlocalmemory/summaries/daily_reflection.py +293 -0
  83. package/src/superlocalmemory/summaries/project_work_log.py +424 -0
  84. package/src/superlocalmemory/summaries/session_summary.py +307 -0
  85. package/src/superlocalmemory/ui/css/design-system.css +76 -1
  86. package/src/superlocalmemory/ui/index.html +28 -11
  87. package/src/superlocalmemory/ui/js/brain.js +43 -7
  88. package/src/superlocalmemory/ui/js/od-agents.js +49 -5
  89. package/src/superlocalmemory/ui/js/od-brain.js +280 -84
  90. package/src/superlocalmemory/ui/js/od-graph.js +147 -6
@@ -15,7 +15,7 @@
15
15
 
16
16
  var GRAPH_URL = '/api/graph';
17
17
  var RECALL_URL = '/api/search'; /* POST {query, limit} -> {results:[...]} */
18
- var MAX_NODES = 120; /* default budget; slider range 20-2000 */
18
+ var MAX_NODES = 50; /* default budget; owner-set to 50; slider range 20-2000 */
19
19
  /* Keep force-layout work bounded even when a user asks to render "All". */
20
20
  var PHYSICS_MAX_NODES = 160;
21
21
  var PRE_SETTLE_TICKS = 24;
@@ -45,12 +45,17 @@
45
45
  /* settle-freeze: stop the rAF loop once kinetic energy drops below SETTLE_KE */
46
46
  var frames = 0, _ke = 0;
47
47
  var SETTLE_MIN = 30, SETTLE_KE = 0.2, SETTLE_MAX_FRAMES = 180;
48
+ /* Bounded wait for layout when the stage reports 0x0. ~3s at 60fps:
49
+ long enough for a cold load whose pane is laid out a beat after
50
+ mount, short enough that a hidden pane cannot hold the rAF loop
51
+ open indefinitely. */
52
+ var blindFrames = 0, BLIND_MAX_FRAMES = 180;
48
53
  var NODES = [], LINKS = [], idx = {};
49
54
  var tierVisible = { 1: true, 2: true, 3: true }; /* independent tier toggles */
50
55
  var scale = 1, ox = 0, oy = 0, dpr = Math.max(1, window.devicePixelRatio || 1);
51
56
  var W = 0, H = 0, selected = null, hover = null;
52
57
  var dragNode = null, panning = false, last = null;
53
- var PAL = {}, themeObs = null;
58
+ var PAL = {}, themeObs = null, sizeObs = null;
54
59
 
55
60
  /* bound handlers kept so re-render can detach them */
56
61
  var onMove = null, onUp = null;
@@ -74,7 +79,12 @@
74
79
  '</div>' +
75
80
  '<label class="chip" style="gap:8px" title="How many nodes to show">' +
76
81
  '<span data-ic="filter"></span> Nodes ' +
77
- '<input id="odg-budget" type="range" min="20" max="2000" step="20" value="' + MAX_NODES + '" style="width:96px;accent-color:var(--violet)">' +
82
+ /* step=10, not 20: the default budget is 50, and a step-20 grid
83
+ starting at min=20 only permits 20/40/60/... so the browser
84
+ silently snapped the control to 60 while the label beside it
85
+ printed MAX_NODES as 50 — the control and its own label
86
+ disagreed on first paint. */
87
+ '<input id="odg-budget" type="range" min="20" max="2000" step="10" value="' + MAX_NODES + '" style="width:96px;accent-color:var(--violet)">' +
78
88
  '<span class="cnt" id="odg-budgetv">' + MAX_NODES + '</span>' +
79
89
  '</label>' +
80
90
  '<button id="odg-showall" class="chip" style="padding:2px 10px;font-size:12px;cursor:pointer" title="Show all nodes (up to 2000)">All</button>' +
@@ -86,9 +96,32 @@
86
96
  '<button id="odg-zout" aria-label="Zoom out">-</button>' +
87
97
  '<button id="odg-zfit" aria-label="Fit" title="Fit to view">&#10562;</button>' +
88
98
  '</div>' +
99
+ /* odg-panel-toggle: below 1100 px the graph-shell collapses to a single
100
+ column and the inspector / Ask-your-memory / Quick Insights stack below
101
+ the 60vh stage — out of view with nothing indicating they are there.
102
+ This button floats at centre-bottom of the stage overlay, tells the
103
+ user the panel exists, and scrolls it into view on click or Enter.
104
+ CSS hides it at >1100px where the panel is already beside the stage
105
+ in the two-column grid (a permanent toggle there is clutter). */
106
+ '<button id="odg-panel-toggle" ' +
107
+ 'aria-label="Show inspector and Ask your memory panel">' +
108
+ 'Details &amp; Chat &#x2193;' +
109
+ '</button>' +
89
110
  '</div>' +
90
111
  '</div>' +
91
- '<aside class="inspector">' +
112
+ /* tabindex="-1" makes the inspector programmatically focusable so the
113
+ odg-panel-toggle onclick can re-anchor keyboard focus here after the
114
+ scroll, preventing the "focus is off-screen" accessibility gap.
115
+ tabindex="-1" is NOT a tab stop — it keeps the element out of the
116
+ natural tab order while allowing focus() from code only. */
117
+ '<aside class="inspector" tabindex="-1">' +
118
+ /* odg-back-btn: companion to #odg-panel-toggle. Visible only at ≤1100px
119
+ (same breakpoint). Appears at the very top of the stacked inspector so
120
+ keyboard users can return to the graph stage without tabbing through all
121
+ inspector content first. CSS hides it at >1100px. */
122
+ '<button class="odg-back-btn" aria-label="Back to graph canvas">' +
123
+ '&#x2191; Graph' +
124
+ '</button>' +
92
125
  '<div class="inspector-scroll" id="odg-insp">' +
93
126
  '<div class="inspector-empty"><div style="font-size:34px;margin-bottom:8px">&#9671;</div>' +
94
127
  'Loading your knowledge graph…</div>' +
@@ -242,7 +275,17 @@
242
275
 
243
276
  function resize() {
244
277
  if (!stage || !cv) return;
245
- W = stage.clientWidth; H = stage.clientHeight;
278
+ /* Read into locals and only COMMIT to W/H once they are real.
279
+ A stage that reports 0x0 is not yet laid out (hidden tab, off-screen
280
+ pane). Drawing into a zero-sized canvas produces the blank-on-cold-load
281
+ failure the owner reported. Equally important: writing 0 into W/H before
282
+ bailing would POISON the last-known-good size — a window resize while the
283
+ graph pane is hidden would zero them, and loop() would then have no valid
284
+ dimensions to work with. Keep the previous good values instead; the
285
+ ResizeObserver re-calls resize() the moment the stage has real pixels. */
286
+ var w = stage.clientWidth, h = stage.clientHeight;
287
+ if (!w || !h) return;
288
+ W = w; H = h;
246
289
  cv.width = W * dpr; cv.height = H * dpr; cv.style.width = W + 'px'; cv.style.height = H + 'px';
247
290
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
248
291
  }
@@ -316,6 +359,27 @@
316
359
 
317
360
  function loop() {
318
361
  if (!running) return;
362
+ /* Blind frame: the stage has no size yet. Two failure modes to avoid, and
363
+ they pull in opposite directions —
364
+ (a) Spending the settle budget while blind. fit() derives scale/ox/oy
365
+ from W/H, so frames rendered at 0x0 park the camera off-screen:
366
+ measured 16,362 arc() calls, every one at x ~= -2,300, canvas
367
+ visually empty. That was the original "blank until I move the node
368
+ slider" bug.
369
+ (b) Idling forever. Re-scheduling rAF unconditionally kept a 60fps
370
+ callback alive on a hidden pane — the graph stays mounted after you
371
+ navigate away, so one window resize while hidden burned CPU
372
+ indefinitely.
373
+ So: retry for a BOUNDED window, then park. The retry covers a cold load
374
+ where layout lands a beat after mount (parking immediately there left the
375
+ first visit blank while re-visits worked). The bound means a hidden pane
376
+ can never hold the loop open — after ~3s it stops, and the ResizeObserver
377
+ calls wake() if the stage ever gains size. */
378
+ if (!W || !H) {
379
+ if (blindFrames++ < BLIND_MAX_FRAMES) { raf = requestAnimationFrame(loop); return; }
380
+ running = false; raf = null; blindFrames = 0; return;
381
+ }
382
+ blindFrames = 0;
319
383
  tick();
320
384
  if (fitFrames < 90) { fit(); fitFrames++; } /* keep settling cloud in view */
321
385
  draw();
@@ -328,6 +392,11 @@
328
392
 
329
393
  /* wake: restart physics after a perturbation (drag, toggle, budget change) */
330
394
  function wake() { frames = 0; if (!running) { running = true; loop(); } }
395
+ /* Window resize: resize() wipes the canvas, so re-fit and repaint after it.
396
+ Named (not an inline closure) so teardown() can actually remove it — the
397
+ previous inline `resize` binding was re-added on every mount and never
398
+ removed, so listeners accumulated one per pane visit. */
399
+ function onWinResize() { resize(); fitFrames = 0; wake(); }
331
400
  /* redraw: single repaint when sim is frozen (pan/zoom/hover) */
332
401
  function redraw() { if (!running) draw(); }
333
402
 
@@ -486,6 +555,37 @@
486
555
  function wireControls() {
487
556
  stage = q('#odg-stage'); cv = q('#odg-cv'); ctx = cv.getContext('2d');
488
557
 
558
+ /* On a cold tab load the graph pane is inside a CSS-hidden tab; the browser
559
+ reports clientWidth/clientHeight = 0 until the tab is actually shown.
560
+ resize() now refuses to commit to a 0×0 canvas (the root cause of the
561
+ "blank on first load, paints after slider drag" bug). ResizeObserver is
562
+ the platform-correct remedy: it fires exactly once when the stage first
563
+ acquires real pixel dimensions, without polling and without a fixed
564
+ setTimeout that cannot be proved race-free. */
565
+ sizeObs = new ResizeObserver(function (entries) {
566
+ var entry = entries[0];
567
+ if (!entry) return;
568
+ var rect = entry.contentRect;
569
+ if (rect.width > 0 && rect.height > 0) {
570
+ /* ALWAYS re-fit and repaint after a size change — never conditionally.
571
+ resize() assigns cv.width, and assigning a canvas's width WIPES it,
572
+ even when the value is unchanged. So any call to resize() that is
573
+ not followed by a repaint leaves a blank canvas on screen.
574
+ An earlier version of this handler only repainted when the canvas
575
+ had previously been blind. Returning to the pane then hit the other
576
+ branch: W/H were stale-but-non-zero, resize() wiped the canvas, and
577
+ nothing redrew it — measured as 53,083 painted pixels on the first
578
+ visit and exactly 0 on the second, with no draw calls issued at all.
579
+ Re-fitting also matters because the pane can come back at a
580
+ different size, and a camera fitted to the old bounds frames the
581
+ wrong region. The settle budget is bounded, so waking here is cheap. */
582
+ resize();
583
+ fitFrames = 0; frames = 0;
584
+ wake();
585
+ }
586
+ });
587
+ sizeObs.observe(stage);
588
+
489
589
  /* tier buttons -- independent toggles: each click flips visibility + re-renders */
490
590
  mount.querySelectorAll('#odg-tier button').forEach(function (b) {
491
591
  b.onclick = function () {
@@ -527,6 +627,30 @@
527
627
  q('#odg-send').onclick = sendAsk;
528
628
  q('#odg-ask').addEventListener('keydown', function (e) { if (e.key === 'Enter') sendAsk(); });
529
629
 
630
+ /* odg-panel-toggle / odg-back-btn — narrow-viewport affordances (≤1100px).
631
+ Both buttons are display:none at >1100px (CSS), so clicks can never fire
632
+ on a wide desktop. scrollIntoView does NOT call resize() and does NOT assign
633
+ cv.width — the canvas is never wiped, the render loop is undisturbed. */
634
+ var panelToggle = q('#odg-panel-toggle');
635
+ var inspector = q('.inspector');
636
+ var backBtn = q('.odg-back-btn');
637
+ if (panelToggle && inspector) {
638
+ panelToggle.onclick = function () {
639
+ inspector.scrollIntoView({ behavior: 'smooth', block: 'start' });
640
+ /* Re-anchor keyboard focus to the inspector so users are not left with
641
+ focus on an off-screen button after the scroll animation completes. */
642
+ inspector.focus({ preventScroll: true });
643
+ };
644
+ }
645
+ if (backBtn) {
646
+ backBtn.onclick = function () {
647
+ var st = q('#odg-stage');
648
+ if (st) st.scrollIntoView({ behavior: 'smooth', block: 'start' });
649
+ /* Return focus to the panel toggle so the user can re-invoke it. */
650
+ if (panelToggle) panelToggle.focus({ preventScroll: true });
651
+ };
652
+ }
653
+
530
654
  // Quick Insight Actions — delegated on mount so buttons survive od-graph.js re-renders.
531
655
  // quick-actions.js:fetchInsight() is global (non-IIFE); it writes into #insight-results
532
656
  // which is mounted inside odg-insights-panel above.
@@ -614,8 +738,22 @@
614
738
  if (onMove) { window.removeEventListener('mousemove', onMove); onMove = null; }
615
739
  if (onUp) { window.removeEventListener('mouseup', onUp); onUp = null; }
616
740
  if (themeObs) { themeObs.disconnect(); themeObs = null; }
741
+ if (sizeObs) { sizeObs.disconnect(); sizeObs = null; }
742
+ window.removeEventListener('resize', onWinResize);
617
743
  }
618
744
 
745
+ /* Legacy compatibility — js/knowledge-graph.js was retired in 4.0.6 and it
746
+ owned window.loadGraph (it assigned loadGraphSigma). Several live callers
747
+ still invoke it, and two of them do so WITHOUT a typeof guard:
748
+ core.js:552 loadGraph() on every page init
749
+ event-delegation.js:42 'load-graph' action
750
+ (core.js:498, od-shell.js:481 and ng-shell.js:345 are guarded and would
751
+ have degraded quietly.) Without this shim those two would throw
752
+ ReferenceError on first paint. Re-fetching when the pane is mounted is the
753
+ honest equivalent of what the old function did; when nothing is mounted the
754
+ pane renders on activation via odRenderGraph, so there is nothing to do. */
755
+ window.loadGraph = function () { if (mount) load(); };
756
+
619
757
  /* ---------------- public entry ---------------- */
620
758
  window.odRenderGraph = function (container) {
621
759
  teardown(); /* re-render safe */
@@ -627,7 +765,10 @@
627
765
  readPalette();
628
766
  wireControls();
629
767
  resize();
630
- window.addEventListener('resize', resize);
768
+ /* Same hazard as the ResizeObserver above: resize() wipes the canvas, so a
769
+ bare `resize` listener leaves the graph blank after a window resize until
770
+ something else happens to repaint. Re-fit and wake instead. */
771
+ window.addEventListener('resize', onWinResize);
631
772
  themeObs = new MutationObserver(readPalette);
632
773
  themeObs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
633
774
  load();