superlocalmemory 4.0.6 → 4.0.7
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 +55 -4
- package/README.md +6 -11
- 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/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 +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/pyproject.toml +1 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +14 -0
- package/src/superlocalmemory/cli/main.py +9 -0
- package/src/superlocalmemory/cli/summary_cmd.py +195 -0
- package/src/superlocalmemory/code_graph/bridge/entity_resolver.py +26 -0
- package/src/superlocalmemory/code_graph/bridge/event_listeners.py +14 -3
- package/src/superlocalmemory/code_graph/bridge/maintenance.py +206 -0
- package/src/superlocalmemory/code_graph/config.py +65 -1
- package/src/superlocalmemory/core/fact_consolidator.py +24 -1
- package/src/superlocalmemory/core/maintenance.py +51 -1
- package/src/superlocalmemory/mcp/tools_code_graph.py +47 -3
- package/src/superlocalmemory/server/routes/memories.py +61 -0
- package/src/superlocalmemory/storage/schema_code_graph.py +44 -1
- package/src/superlocalmemory/ui/index.html +1 -1
- package/src/superlocalmemory/ui/js/fact-detail.js +61 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,54 @@ All notable changes to SuperLocalMemory will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [4.0.7] — Reachable
|
|
9
|
+
|
|
10
|
+
Everything here was already built. None of it could be used.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **`slm summary` — the readable layer over your memories.** Requested in #113
|
|
14
|
+
and shipped without a way to call it in 4.0.6.
|
|
15
|
+
- `slm summary day` — what you recorded today (also `yesterday`, or a date)
|
|
16
|
+
- `slm summary project` — what was worked on, for a directory
|
|
17
|
+
- `slm summary session <id>` — what one session covered
|
|
18
|
+
|
|
19
|
+
Every summary says how much of the underlying data it could actually see, and
|
|
20
|
+
`--json` lists the exact memories it was built from. Session data is sparse —
|
|
21
|
+
roughly 4% of facts carry a session id — so a session summary reports that
|
|
22
|
+
rather than presenting a fraction as the whole. No language model needed, so
|
|
23
|
+
these work in the fully offline mode.
|
|
24
|
+
- **Memories now link to the code they mention.** A memory that names a
|
|
25
|
+
function, method or file is connected to it in the code graph, with a short
|
|
26
|
+
description of what it points at and a marker when the code has since changed.
|
|
27
|
+
Expanding a memory in the dashboard shows this; nothing appears if you have no
|
|
28
|
+
code graph. Runs during background maintenance, never when a memory is saved,
|
|
29
|
+
and requires no language model.
|
|
30
|
+
- **`scripts/bump_version.py`** sets the release version in all fifteen places
|
|
31
|
+
that declare one, with `--check` for CI.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- **The code↔memory bridge never ran.** Four things were missing at once: setup
|
|
35
|
+
wrote a `bridge_enabled` flag no code read, the code-graph build discarded it,
|
|
36
|
+
the code-graph settings had no loader at all, and the method the bridge was
|
|
37
|
+
written against was an unimplemented placeholder. Enabling the code graph
|
|
38
|
+
during setup left the flag on disk and the feature inert, with nothing to
|
|
39
|
+
indicate it.
|
|
40
|
+
- **Version numbers disagreed across the project.** 4.0.6 shipped with the pip
|
|
41
|
+
requirements pins, the npm lockfile, the editor plugin manifest, the citation
|
|
42
|
+
metadata and the lockfile all still reading 4.0.5, and the agent rule footers
|
|
43
|
+
reading 4.0.4. Installing from `requirements.txt` fetched the wrong release.
|
|
44
|
+
- **Stale-memory checks reported "nothing is stale" when the feature was off.**
|
|
45
|
+
They answer entirely from code links, so with linking disabled they returned an
|
|
46
|
+
empty list — the most reassuring possible answer, from something that never
|
|
47
|
+
ran. They now say the feature is off, and the remediation message names a
|
|
48
|
+
setting that exists (it previously named one that did not).
|
|
49
|
+
- **`slm help` did not mention `gdpr`**, which shipped in 4.0.6. The drift guard
|
|
50
|
+
that should have caught it only read one file, so a command registered
|
|
51
|
+
elsewhere was exempt from the check.
|
|
52
|
+
- **Consolidation could create a database file named after a bad argument.**
|
|
53
|
+
Given something that was neither a database handle nor a path, it stringified
|
|
54
|
+
the object and let SQLite create that filename. It now refuses the argument.
|
|
55
|
+
|
|
8
56
|
## [4.0.6] — The Connected Brain
|
|
9
57
|
|
|
10
58
|
### Added
|
|
@@ -14,10 +62,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
14
62
|
a starting value as a measured result. Source quality no longer lists internal
|
|
15
63
|
identifiers; when nothing has been measured yet it says so and explains what
|
|
16
64
|
would change that.
|
|
17
|
-
- **Session, daily and project summaries
|
|
18
|
-
what a session covered, what a day's main
|
|
19
|
-
per project. Each
|
|
20
|
-
how much of the underlying data it could
|
|
65
|
+
- **Session, daily and project summaries — generators only, not yet reachable.**
|
|
66
|
+
A readable layer over your memories: what a session covered, what a day's main
|
|
67
|
+
topics were, and what was worked on per project. Each links back to the
|
|
68
|
+
memories it came from and states how much of the underlying data it could
|
|
69
|
+
actually cover. Corrected after release: 4.0.6 ships the generators but no
|
|
70
|
+
command, tool or endpoint that calls them, so there is no way to use this yet.
|
|
71
|
+
The surface lands in 4.0.7. Requested in #113.
|
|
21
72
|
- **Entity-level memory consolidation now runs.** Repeated facts about the same
|
|
22
73
|
entity are merged during maintenance, and the originals are archived rather
|
|
23
74
|
than deleted.
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
</picture>
|
|
6
6
|
</p>
|
|
7
7
|
|
|
8
|
-
<h1 align="center">SuperLocalMemory V4.0.
|
|
8
|
+
<h1 align="center">SuperLocalMemory V4.0.7</h1>
|
|
9
9
|
|
|
10
10
|
<h2 align="center">Rent the LLM. Own the memory.</h2>
|
|
11
11
|
|
|
@@ -27,12 +27,12 @@ guarantee here is stated as a falsifiable invariant, tested under an adversarial
|
|
|
27
27
|
negative control, and shipped with the harness that regenerates the evidence:
|
|
28
28
|
<code>python benchmark/run_all.py --trials 200 --output-dir results/</code>. What each experiment
|
|
29
29
|
does <em>not</em> exercise is stated too.</p>
|
|
30
|
-
<p align="center"><code>v4.0.
|
|
30
|
+
<p align="center"><code>v4.0.7</code> — one control plane: <strong>SLM-Mesh</strong> peer coordination · multi-scope memory (personal / shared / global) · profiles · Cache · Compress · 7-layer retrieval · code graph · Entity Explorer · skill evolution · Modes A/B/C · GDPR retention & audit chain · bounded loops — across CLI, MCP, dashboard, the <strong>Claude plugin</strong>, the <strong>Codex add-on</strong>, and documented IDE integrations.<br/>
|
|
31
31
|
Proxy: <code>slm wrap claude</code> · MCP: add <code>slm_compress</code> to your config · Skill: zero-config</p>
|
|
32
32
|
<p align="center"><strong>Four public arXiv preprints</strong> · V4: <a href="https://arxiv.org/abs/2608.08253">arXiv:2608.08253</a> · companion archive: <a href="https://zenodo.org/records/21853302">Zenodo 21853302</a> (<a href="https://doi.org/10.5281/zenodo.21853302">DOI 10.5281/zenodo.21853302</a>) · prior preprints: <a href="https://arxiv.org/abs/2603.02240">2603.02240</a> · <a href="https://arxiv.org/abs/2603.14588">2603.14588</a> · <a href="https://arxiv.org/abs/2604.04514">2604.04514</a>.</p>
|
|
33
33
|
|
|
34
34
|
<p align="center">
|
|
35
|
-
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/v4.0.
|
|
35
|
+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/v4.0.7-Current_Release-2ea44f?style=for-the-badge&logo=checkmarx&logoColor=white" alt="v4.0.7 — Current Release"/></a>
|
|
36
36
|
<a href="https://arxiv.org/abs/2608.08253"><img src="https://img.shields.io/badge/arXiv-2608.08253-b31b1b?style=for-the-badge&logo=arxiv&logoColor=white" alt="SuperLocalMemory 4.0 paper on arXiv:2608.08253"/></a>
|
|
37
37
|
<a href="https://zenodo.org/records/21853302"><img src="https://img.shields.io/badge/Zenodo-10.5281%2Fzenodo.21853302-1682D4?style=for-the-badge&logo=zenodo&logoColor=white" alt="V4 paper on Zenodo: 10.5281/zenodo.21853302"/></a>
|
|
38
38
|
<a href="https://arxiv.org/abs/2603.14588"><img src="https://img.shields.io/badge/arXiv-2603.14588-b31b1b?style=for-the-badge&logo=arxiv&logoColor=white" alt="arXiv Paper"/></a>
|
|
@@ -62,16 +62,11 @@ SuperLocalMemory V4 combines conventional dense and lexical retrieval with graph
|
|
|
62
62
|
|
|
63
63
|
**Memory with a sense of time.** SLM does not only store *what* an agent learned — it records *when*. Every fact carries ingestion timing and provenance; recall runs a dedicated temporal candidate channel alongside semantic, lexical, and associative retrieval; scenes and entity timelines reconstruct sequence; and the lifecycle lets neglected memory decay and self-archive instead of growing without bound. Time is a first-class ranking and lifecycle signal rather than a timestamp column an agent never reads — which is what lets a long-lived agent reason about how its context changed, not only what it currently holds.
|
|
64
64
|
|
|
65
|
-
**What V4.0.
|
|
65
|
+
**What V4.0.7 ships.** Three things that existed but could not be used. `slm summary` gives you a readable layer over your own memories — `day` for what you recorded today, `project` for a directory, `session` for one session — each stating how much of the underlying data it could actually see, with `--json` listing the exact memories it came from. Memories that mention a function, method or file are now linked to that code, with a short description of what they point at and a marker once the code has changed; expanding a memory in the dashboard shows it. Both need no language model, so they work in the fully local mode. The code↔memory bridge behind the second one had never run at all: the setup flag was written and never read, the build discarded it, its settings had no loader, and the method it was written against was an unimplemented placeholder. Linking runs during background maintenance, never when a memory is saved. See [reviewed corrections](docs/reviewed-corrections.md) for the correction lifecycle and [MCP tools](docs/mcp-tools.md) for host-facing commands.
|
|
66
66
|
|
|
67
|
-
**Fixed in V4.0.
|
|
67
|
+
**Fixed in V4.0.7.** Version numbers disagreed across the project — the pip requirement pins, npm lockfile, editor plugin manifest, citation metadata and lockfile all still named the previous release, so installing from `requirements.txt` fetched the wrong version; one script now sets all fifteen. Stale-memory checks reported "nothing is stale" when code linking was simply switched off, and pointed at a setting that did not exist. `slm gdpr` was missing from `slm help`. Consolidation, handed something that was neither a database handle nor a path, created a file named after the object instead of refusing it.
|
|
68
68
|
|
|
69
|
-
**Carried forward from V4.0.5.** A correction is a review-gated lifecycle, not an in-place edit: SLM creates an immutable successor, keeps it out of current recall until an authenticated reviewer applies it, and preserves the predecessor for time-aware history. Every candidate path
|
|
70
|
-
|
|
71
|
-
**Adaptive-ranking migration.** V4.0.6 leaves the optional adaptive ranker off
|
|
72
|
-
unless an operator sets `SLM_RANKING` (`v1`, `v2`, or `v2-ensemble`). This does
|
|
73
|
-
not disable the normal retrieval channels; it prevents feedback and
|
|
74
|
-
observation data from changing ranking without an explicit operator decision.
|
|
69
|
+
**Carried forward from V4.0.5 and V4.0.6.** A correction is a review-gated lifecycle, not an in-place edit: SLM creates an immutable successor, keeps it out of current recall until an authenticated reviewer applies it, and preserves the predecessor for time-aware history. Every candidate path — cached context, pins, bridge and scene expansion — uses hard current-truth admission and abstains if that truth cannot be read. `slm brain`, MCP, HTTP and the Living Brain share one observation-only BrainTruth snapshot; feedback, external Bounded Loops evidence and receipt claims are shown honestly but never silently alter recall, ranking or model routing. The Living Brain leads with how many questions your memory has answered rather than a raw event count, and says so plainly where nothing has been measured yet. The knowledge graph opens reliably, with a default of 50 nodes and its details panel reachable on narrow screens. The optional adaptive ranker stays off unless an operator sets `SLM_RANKING` (`v1`, `v2`, or `v2-ensemble`) — that gate prevents feedback and observation data from changing ranking without an explicit decision, and does not disable the normal retrieval channels.
|
|
75
70
|
|
|
76
71
|
- **[SLM-Mesh](#slm-mesh-cross-session--cross-machine-coordination)** — authenticated cross-session and cross-machine peer coordination (messages, locks, shared state, inbox/outbox, optional discovery). Coordination only — not automatic replicated memory.
|
|
77
72
|
- **Multi-scope memory & profiles** — workspaces (profiles) plus `personal` / `shared` / `global` scopes; cross-profile recall is default-deny.
|
package/package.json
CHANGED
package/plugin/CLAUDE.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- BEGIN SuperLocalMemory v4.0.
|
|
1
|
+
<!-- BEGIN SuperLocalMemory v4.0.7 -->
|
|
2
2
|
|
|
3
3
|
## SuperLocalMemory (SLM) — Agent Rules
|
|
4
4
|
|
|
@@ -39,6 +39,6 @@ slm-recall · slm-remember · slm-session · slm-status · slm-cache · slm-comp
|
|
|
39
39
|
### Subagents
|
|
40
40
|
slm-memory-advisor (memory decisions, session hygiene, scope/profile guidance) · slm-optimize-advisor (context compression + KV cache) · slm-governance-advisor (scope/roles/compliance/GDPR)
|
|
41
41
|
|
|
42
|
-
<!-- END SuperLocalMemory v4.0.
|
|
42
|
+
<!-- END SuperLocalMemory v4.0.7 -->
|
|
43
43
|
|
|
44
|
-
SuperLocalMemory v4.0.
|
|
44
|
+
SuperLocalMemory v4.0.7 · Qualixar · AGPL-3.0-or-later
|
|
@@ -77,4 +77,4 @@ slm-scope · slm-governance · slm-profile · slm-remember · slm-recall
|
|
|
77
77
|
# What NOT to do
|
|
78
78
|
Never session_init twice; never forget without dry-run preview; never store secrets; never bypass role checks; never claim an erasure succeeded without verifying via recall.
|
|
79
79
|
|
|
80
|
-
SuperLocalMemory v4.0.
|
|
80
|
+
SuperLocalMemory v4.0.7 · Qualixar · AGPL-3.0-or-later
|
|
@@ -46,4 +46,4 @@ slm-recall · slm-remember · slm-session · slm-scope · slm-profile · slm-gov
|
|
|
46
46
|
# What NOT to do
|
|
47
47
|
Never session_init twice; never forget dry_run=False without reporting preview; never dump a whole file into remember; never invent a memory; never claim "saved" without success:true / clean CLI exit; never bypass scope or governance restrictions.
|
|
48
48
|
|
|
49
|
-
SuperLocalMemory v4.0.
|
|
49
|
+
SuperLocalMemory v4.0.7 · Qualixar · AGPL-3.0-or-later
|
|
@@ -41,4 +41,4 @@ slm-compress · slm-cache · slm-status · slm-profile
|
|
|
41
41
|
# What NOT to do
|
|
42
42
|
Never compress code-for-edit/JSON-to-parse/<500 chars; never store secrets/ccr_ids; never let optimize failure block/alter the task; never claim a specific savings %; never carry ccr_ids across profile switches.
|
|
43
43
|
|
|
44
|
-
SuperLocalMemory v4.0.
|
|
44
|
+
SuperLocalMemory v4.0.7 · Qualixar · AGPL-3.0-or-later
|
package/plugin/requirements.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
superlocalmemory==4.0.
|
|
1
|
+
superlocalmemory==4.0.7
|
|
@@ -129,4 +129,4 @@ When the SLM MCP server is unavailable, use these CLI equivalents:
|
|
|
129
129
|
- **slm-optimize-advisor** — context compression and KV cache
|
|
130
130
|
- **slm-governance-advisor** — scope/role compliance, retention policies, GDPR
|
|
131
131
|
|
|
132
|
-
SuperLocalMemory v4.0.
|
|
132
|
+
SuperLocalMemory v4.0.7 · Qualixar · AGPL-3.0-or-later
|
package/pyproject.toml
CHANGED
|
@@ -32,7 +32,7 @@ if "OMP_NUM_THREADS" not in os.environ:
|
|
|
32
32
|
os.environ["OMP_NUM_THREADS"] = "2"
|
|
33
33
|
# ---------------------------------------------------------------------------
|
|
34
34
|
|
|
35
|
-
__version__ = "4.0.
|
|
35
|
+
__version__ = "4.0.7"
|
|
36
36
|
|
|
37
37
|
_REQUIRED_VERSIONS = {
|
|
38
38
|
"sentence_transformers": "5.3.0",
|
|
@@ -282,6 +282,12 @@ def _cmd_gdpr_dispatch(args: Namespace) -> None:
|
|
|
282
282
|
cmd_gdpr(args)
|
|
283
283
|
|
|
284
284
|
|
|
285
|
+
def _cmd_summary_dispatch(args: Namespace) -> None:
|
|
286
|
+
"""V4.0.7: readable session/day/project summaries (issue #113)."""
|
|
287
|
+
from superlocalmemory.cli.summary_cmd import cmd_summary
|
|
288
|
+
cmd_summary(args)
|
|
289
|
+
|
|
290
|
+
|
|
285
291
|
# ---- end SLM v3.6 Optimize dispatch functions ----
|
|
286
292
|
|
|
287
293
|
|
|
@@ -414,6 +420,7 @@ def dispatch(args: Namespace) -> None:
|
|
|
414
420
|
"ops": _cmd_ops,
|
|
415
421
|
# V4.0.6: GDPR subject-rights CLI (Art.15/17/20)
|
|
416
422
|
"gdpr": _cmd_gdpr_dispatch,
|
|
423
|
+
"summary": _cmd_summary_dispatch,
|
|
417
424
|
}
|
|
418
425
|
handler = handlers.get(args.command)
|
|
419
426
|
if handler:
|
|
@@ -2215,6 +2222,13 @@ _COMMAND_GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
|
|
|
2215
2222
|
("forget", "Run the decay cycle (preview first)"),
|
|
2216
2223
|
("trace", "Recall with a per-channel score breakdown"),
|
|
2217
2224
|
("ingest", "Ingest external observations / documents"),
|
|
2225
|
+
("summary", "Readable summaries: session, day, or project"),
|
|
2226
|
+
]),
|
|
2227
|
+
("Privacy & compliance", [
|
|
2228
|
+
# gdpr shipped in 4.0.6 but was never listed here, so `slm help` did not
|
|
2229
|
+
# mention it at all. The drift test caught it; the omission is the thing
|
|
2230
|
+
# that test exists to prevent.
|
|
2231
|
+
("gdpr", "Subject rights: status, export, erase, verify"),
|
|
2218
2232
|
]),
|
|
2219
2233
|
("Run SLM (daemon & dashboard)", [
|
|
2220
2234
|
("serve", "Start/stop the background daemon"),
|
|
@@ -90,6 +90,9 @@ _NO_DAEMON_COMMANDS = {
|
|
|
90
90
|
"serve", "restart",
|
|
91
91
|
# V4.0.6: GDPR CLI accesses the DB directly; no daemon required.
|
|
92
92
|
"gdpr",
|
|
93
|
+
# V4.0.7: summaries read memory.db directly and are extractive by default,
|
|
94
|
+
# so they need neither the daemon nor a language model.
|
|
95
|
+
"summary",
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
|
|
@@ -968,6 +971,12 @@ def main() -> None:
|
|
|
968
971
|
_sp.add_argument("--json", action="store_true",
|
|
969
972
|
help="Output structured JSON (agent-native)")
|
|
970
973
|
|
|
974
|
+
# V4.0.7: the readable summary layer from issue #113. The generators shipped
|
|
975
|
+
# in 4.0.6 with no caller; this is the surface that makes them reachable.
|
|
976
|
+
from superlocalmemory.cli.summary_cmd import register_summary_parser
|
|
977
|
+
|
|
978
|
+
register_summary_parser(sub)
|
|
979
|
+
|
|
971
980
|
# Wave-3 / V4.0.6: GDPR subject-rights CLI (Art.15/17/20)
|
|
972
981
|
gdpr_p = sub.add_parser(
|
|
973
982
|
"gdpr",
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory | https://qualixar.com
|
|
4
|
+
|
|
5
|
+
"""``slm summary`` — the readable layer over your memories (issue #113).
|
|
6
|
+
|
|
7
|
+
WHY THIS FILE EXISTS
|
|
8
|
+
--------------------
|
|
9
|
+
4.0.6 shipped the three generators in ``superlocalmemory/summaries/`` with no way
|
|
10
|
+
to call them: no command, no MCP tool, no route. The changelog listed the feature
|
|
11
|
+
as added, the issue reply said it had landed, and a user could do nothing with it.
|
|
12
|
+
This is that missing surface.
|
|
13
|
+
|
|
14
|
+
Three summaries, each bounded and traceable:
|
|
15
|
+
|
|
16
|
+
``slm summary session <id>`` what one session covered
|
|
17
|
+
``slm summary day [DATE]`` what a day's main topics were
|
|
18
|
+
``slm summary project <path>`` what was worked on in a project
|
|
19
|
+
|
|
20
|
+
Every result states its coverage. Session data in particular is sparse — roughly
|
|
21
|
+
4% of facts carry a session id on a real store — so a session summary reports what
|
|
22
|
+
fraction it could actually see rather than presenting a slice as the whole.
|
|
23
|
+
|
|
24
|
+
No language model is required: the generators are extractive by default, so this
|
|
25
|
+
works in Local Guardian mode with nothing installed.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
from argparse import Namespace
|
|
32
|
+
from datetime import date, timedelta
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from typing import Any
|
|
35
|
+
|
|
36
|
+
from superlocalmemory.infra.data_root import state_path
|
|
37
|
+
|
|
38
|
+
def _coverage_is_complete(coverage: str) -> bool:
|
|
39
|
+
"""Whether *coverage* means "this really is the whole picture".
|
|
40
|
+
|
|
41
|
+
Deliberately inverted. My first version listed the values that needed a
|
|
42
|
+
caveat — ``("partial", "sparse", "none", "empty")`` — and three of those four
|
|
43
|
+
are not values this system emits. The real vocabulary is COVERAGE_FULL /
|
|
44
|
+
PARTIAL / INSUFFICIENT / NO_SESSION / UNAVAILABLE, so a session summary
|
|
45
|
+
reporting "no_session" printed no caveat at all: the one honesty feature
|
|
46
|
+
issue #113 asked for, silently inactive.
|
|
47
|
+
|
|
48
|
+
Testing for completeness instead means any value that is not FULL — including
|
|
49
|
+
one added later — gets the caveat. The failure mode becomes an unnecessary
|
|
50
|
+
warning rather than a missing one.
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
from superlocalmemory.summaries.base import COVERAGE_FULL
|
|
54
|
+
|
|
55
|
+
return coverage == COVERAGE_FULL
|
|
56
|
+
except Exception:
|
|
57
|
+
return coverage == "full"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _db_path() -> Path:
|
|
61
|
+
return state_path("memory.db")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _active_profile() -> str:
|
|
65
|
+
"""Resolve the active profile without a daemon and without side effects.
|
|
66
|
+
|
|
67
|
+
Reads the ``active`` pointer out of ``profiles.json`` directly.
|
|
68
|
+
``ProfileManager`` would give the same answer, but its constructor calls
|
|
69
|
+
``mkdir(parents=True)`` — creating directories is not something a read-only
|
|
70
|
+
summary command should do. ``core.profiles`` also has no module-level
|
|
71
|
+
accessor; ``get_active_profile`` there is a method on the manager, and the
|
|
72
|
+
module-level one lives in ``server/routes/helpers.py``, which the CLI must
|
|
73
|
+
not import.
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
from superlocalmemory.core.profiles import DEFAULT_PROFILES_FILE
|
|
77
|
+
|
|
78
|
+
path = state_path(DEFAULT_PROFILES_FILE)
|
|
79
|
+
if path.exists():
|
|
80
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
81
|
+
active = raw.get("active")
|
|
82
|
+
if isinstance(active, str) and active:
|
|
83
|
+
return active
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
86
|
+
return "default"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _emit(result: Any, as_json: bool) -> None:
|
|
90
|
+
"""Print a SummaryResult as JSON or as prose."""
|
|
91
|
+
if as_json:
|
|
92
|
+
print(json.dumps({
|
|
93
|
+
"kind": result.kind,
|
|
94
|
+
"profile_id": result.profile_id,
|
|
95
|
+
"content": result.content,
|
|
96
|
+
"source_fact_ids": result.source_fact_ids,
|
|
97
|
+
"coverage": result.coverage,
|
|
98
|
+
"generated_by": result.generated_by,
|
|
99
|
+
"metadata": result.metadata,
|
|
100
|
+
}, indent=2, default=str))
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
print()
|
|
104
|
+
print(result.content.rstrip() or "(nothing recorded)")
|
|
105
|
+
print()
|
|
106
|
+
|
|
107
|
+
# Coverage is not decoration. A summary built from a fraction of the data
|
|
108
|
+
# that presents itself as the whole is the failure mode issue #113 called
|
|
109
|
+
# out by name, so it is stated on every single result, not only bad ones.
|
|
110
|
+
n = len(result.source_fact_ids)
|
|
111
|
+
line = f"Built from {n} memor{'y' if n == 1 else 'ies'} · coverage: {result.coverage}"
|
|
112
|
+
if not _coverage_is_complete(result.coverage):
|
|
113
|
+
line += " — treat as a partial view, not a complete record"
|
|
114
|
+
print(line)
|
|
115
|
+
if result.generated_by:
|
|
116
|
+
print(f"Method: {result.generated_by}")
|
|
117
|
+
print("Use --json to see the exact memories this came from.")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def cmd_summary(args: Namespace) -> None:
|
|
121
|
+
"""Dispatch ``slm summary <subcommand>``."""
|
|
122
|
+
sub = getattr(args, "summary_command", None)
|
|
123
|
+
as_json = bool(getattr(args, "json", False))
|
|
124
|
+
profile = getattr(args, "profile", None) or _active_profile()
|
|
125
|
+
db = _db_path()
|
|
126
|
+
|
|
127
|
+
if not db.exists():
|
|
128
|
+
print(f"No memory database at {db}. Run `slm status` first.")
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
if sub == "session":
|
|
132
|
+
from superlocalmemory.summaries import generate_session_summary
|
|
133
|
+
|
|
134
|
+
_emit(generate_session_summary(db, args.session_id, profile), as_json)
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
if sub == "day":
|
|
138
|
+
from superlocalmemory.summaries import generate_daily_reflection
|
|
139
|
+
|
|
140
|
+
target = getattr(args, "date", None) or date.today().isoformat()
|
|
141
|
+
if target == "yesterday":
|
|
142
|
+
target = (date.today() - timedelta(days=1)).isoformat()
|
|
143
|
+
elif target == "today":
|
|
144
|
+
target = date.today().isoformat()
|
|
145
|
+
_emit(generate_daily_reflection(db, target, profile), as_json)
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
if sub == "project":
|
|
149
|
+
from superlocalmemory.summaries import generate_project_work_log
|
|
150
|
+
|
|
151
|
+
path = getattr(args, "path", None) or str(Path.cwd())
|
|
152
|
+
_emit(generate_project_work_log(db, path, profile), as_json)
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
print("Usage: slm summary {session <id> | day [DATE] | project [PATH]}")
|
|
156
|
+
print()
|
|
157
|
+
print(" slm summary day what you recorded today")
|
|
158
|
+
print(" slm summary day yesterday ...or yesterday")
|
|
159
|
+
print(" slm summary day 2026-08-17 ...or a specific date")
|
|
160
|
+
print(" slm summary project work log for the current directory")
|
|
161
|
+
print(" slm summary session <id> what one session covered")
|
|
162
|
+
print()
|
|
163
|
+
print("Add --json to include the ids of the memories a summary came from.")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def register_summary_parser(sub: Any) -> None:
|
|
167
|
+
"""Attach the ``summary`` parser. Called from cli/main.py."""
|
|
168
|
+
p = sub.add_parser(
|
|
169
|
+
"summary",
|
|
170
|
+
help="Readable summaries of your memories (session, day, project)",
|
|
171
|
+
)
|
|
172
|
+
p.add_argument("--json", action="store_true", help="machine-readable output")
|
|
173
|
+
p.add_argument("--profile", help="profile to summarise (default: active)")
|
|
174
|
+
ssub = p.add_subparsers(dest="summary_command", title="summary subcommands")
|
|
175
|
+
|
|
176
|
+
s = ssub.add_parser("session", help="what one session covered")
|
|
177
|
+
s.add_argument("session_id", help="session id (see `slm status`)")
|
|
178
|
+
s.add_argument("--json", action="store_true")
|
|
179
|
+
s.add_argument("--profile")
|
|
180
|
+
|
|
181
|
+
d = ssub.add_parser("day", help="what a day's main topics were")
|
|
182
|
+
d.add_argument(
|
|
183
|
+
"date", nargs="?",
|
|
184
|
+
help="YYYY-MM-DD, 'today' or 'yesterday' (default: today)",
|
|
185
|
+
)
|
|
186
|
+
d.add_argument("--json", action="store_true")
|
|
187
|
+
d.add_argument("--profile")
|
|
188
|
+
|
|
189
|
+
pr = ssub.add_parser("project", help="what was worked on in a project")
|
|
190
|
+
pr.add_argument(
|
|
191
|
+
"path", nargs="?",
|
|
192
|
+
help="project directory (default: current directory)",
|
|
193
|
+
)
|
|
194
|
+
pr.add_argument("--json", action="store_true")
|
|
195
|
+
pr.add_argument("--profile")
|
|
@@ -152,9 +152,25 @@ class EntityResolver:
|
|
|
152
152
|
self,
|
|
153
153
|
fact_text: str,
|
|
154
154
|
fact_id: str,
|
|
155
|
+
max_links: int | None = None,
|
|
155
156
|
) -> list[CodeMemoryLink]:
|
|
156
157
|
"""Resolve code entity mentions in fact text and create links.
|
|
157
158
|
|
|
159
|
+
Args:
|
|
160
|
+
fact_text: Text to scan for code mentions.
|
|
161
|
+
fact_id: The fact these links belong to.
|
|
162
|
+
max_links: Keep at most this many links, highest confidence first.
|
|
163
|
+
``None`` (the default) is unbounded, preserving the behaviour the
|
|
164
|
+
manual ``link_memory_to_code`` path relies on.
|
|
165
|
+
|
|
166
|
+
A bound matters for automatic resolution. One file-path mention
|
|
167
|
+
matches EVERY node in that file: the fact "the parser in
|
|
168
|
+
code_graph/parser.py was dropping edges" produced 17 links at
|
|
169
|
+
confidence 0.6-0.8, while a backticked function name produces one
|
|
170
|
+
at 0.95. Unbounded, a fact naming a few files buries its own
|
|
171
|
+
high-signal links and hands a large node set to HebbianLinker,
|
|
172
|
+
whose neighbourhood expansion then grows accordingly.
|
|
173
|
+
|
|
158
174
|
Returns list of CodeMemoryLink objects created.
|
|
159
175
|
"""
|
|
160
176
|
if not fact_text or not fact_id:
|
|
@@ -177,6 +193,16 @@ class EntityResolver:
|
|
|
177
193
|
if not matches:
|
|
178
194
|
return []
|
|
179
195
|
|
|
196
|
+
selected = list(matches.values())
|
|
197
|
+
if max_links is not None and len(selected) > max_links:
|
|
198
|
+
# Confidence ranks the match kinds correctly already — backticked and
|
|
199
|
+
# call-syntax mentions score 0.95, a bare identifier 0.9, file-path
|
|
200
|
+
# fan-out 0.6-0.8 — so ordering by it keeps the precise mentions and
|
|
201
|
+
# drops the broad ones.
|
|
202
|
+
selected.sort(key=lambda m: m.confidence, reverse=True)
|
|
203
|
+
selected = selected[:max_links]
|
|
204
|
+
matches = {m.node_id: m for m in selected}
|
|
205
|
+
|
|
180
206
|
# Classify link type
|
|
181
207
|
link_type = self._classify_link_type(fact_text)
|
|
182
208
|
now_str = datetime.now(timezone.utc).isoformat()
|