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.
- package/CHANGELOG.md +90 -0
- package/README.md +23 -14
- package/ide/configs/codex-mcp.toml +2 -2
- package/package.json +3 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +1 -0
- 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/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 +3 -2
- 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 +2 -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 +1 -1
- package/plugin-src/rules/AGENTS.md +6 -5
- package/plugin-src/skills/slm-graph/SKILL.md +2 -1
- package/plugin-src/skills/slm-profile/SKILL.md +1 -0
- package/pyproject.toml +1 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/access/rbac.py +106 -0
- package/src/superlocalmemory/brain/__init__.py +5 -0
- package/src/superlocalmemory/brain/truth.py +418 -0
- package/src/superlocalmemory/cli/__main__.py +17 -0
- package/src/superlocalmemory/cli/commands.py +96 -28
- package/src/superlocalmemory/cli/gdpr_cmd.py +779 -0
- package/src/superlocalmemory/cli/gdpr_io.py +109 -0
- package/src/superlocalmemory/cli/main.py +88 -0
- package/src/superlocalmemory/code_graph/extractors/__init__.py +17 -0
- package/src/superlocalmemory/code_graph/graph_store.py +180 -3
- package/src/superlocalmemory/code_graph/parser.py +280 -100
- package/src/superlocalmemory/compliance/gdpr.py +358 -0
- package/src/superlocalmemory/core/config.py +44 -1
- package/src/superlocalmemory/core/context_cache.py +58 -1
- package/src/superlocalmemory/core/engine_wiring.py +5 -1
- package/src/superlocalmemory/core/maintenance.py +43 -1
- package/src/superlocalmemory/core/mutations.py +155 -25
- package/src/superlocalmemory/core/recall_pipeline.py +6 -10
- package/src/superlocalmemory/core/recall_worker.py +33 -12
- package/src/superlocalmemory/core/remember_runtime.py +271 -2
- package/src/superlocalmemory/core/store_pipeline.py +100 -38
- package/src/superlocalmemory/encoding/consolidator.py +17 -47
- package/src/superlocalmemory/encoding/temporal_validator.py +14 -18
- package/src/superlocalmemory/hooks/user_prompt_hook.py +1 -1
- package/src/superlocalmemory/infra/backup.py +138 -0
- package/src/superlocalmemory/infra/backup_obligations.py +423 -0
- package/src/superlocalmemory/integrations/bounded_loops_mcp.py +4 -3
- package/src/superlocalmemory/learning/engagement.py +165 -0
- package/src/superlocalmemory/mcp/profiles.py +19 -7
- package/src/superlocalmemory/mcp/server.py +4 -2
- package/src/superlocalmemory/mcp/tools_brain.py +54 -10
- package/src/superlocalmemory/mcp/tools_code_graph.py +31 -4
- package/src/superlocalmemory/mcp/tools_core.py +88 -3
- package/src/superlocalmemory/mcp/tools_v3.py +20 -6
- package/src/superlocalmemory/retrieval/engine.py +28 -10
- package/src/superlocalmemory/retrieval/remote_reranker.py +108 -11
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +119 -19
- package/src/superlocalmemory/server/routes/brain.py +297 -14
- package/src/superlocalmemory/server/routes/learning.py +13 -25
- package/src/superlocalmemory/server/routes/memories.py +129 -3
- package/src/superlocalmemory/server/routes/v3_api.py +171 -60
- package/src/superlocalmemory/storage/_migration_internals.py +4 -0
- package/src/superlocalmemory/storage/_schema_version.py +2 -2
- package/src/superlocalmemory/storage/correction_cases.py +670 -0
- package/src/superlocalmemory/storage/database.py +230 -24
- package/src/superlocalmemory/storage/migration_runner.py +7 -0
- package/src/superlocalmemory/storage/migrations/M042_correction_case_ledger.py +245 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +2 -0
- package/src/superlocalmemory/storage/models.py +12 -4
- package/src/superlocalmemory/storage/write_coordinator.py +4 -0
- package/src/superlocalmemory/summaries/__init__.py +37 -0
- package/src/superlocalmemory/summaries/base.py +108 -0
- package/src/superlocalmemory/summaries/daily_reflection.py +293 -0
- package/src/superlocalmemory/summaries/project_work_log.py +424 -0
- package/src/superlocalmemory/summaries/session_summary.py +307 -0
- package/src/superlocalmemory/ui/css/design-system.css +76 -1
- package/src/superlocalmemory/ui/index.html +28 -11
- package/src/superlocalmemory/ui/js/brain.js +43 -7
- package/src/superlocalmemory/ui/js/od-agents.js +49 -5
- package/src/superlocalmemory/ui/js/od-brain.js +280 -84
- 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 =
|
|
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
|
-
|
|
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">⥂</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 & Chat ↓' +
|
|
109
|
+
'</button>' +
|
|
89
110
|
'</div>' +
|
|
90
111
|
'</div>' +
|
|
91
|
-
|
|
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
|
+
'↑ 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">◇</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
|
-
|
|
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
|
-
|
|
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();
|