sessionmem 1.4.0 → 1.6.0

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 (57) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +233 -41
  3. package/dist/adapters/diagnostics.js +109 -0
  4. package/dist/adapters/generic.js +6 -2
  5. package/dist/adapters/global/antigravity.js +4 -1
  6. package/dist/adapters/global/claudeCode.js +19 -4
  7. package/dist/adapters/global/codex.js +7 -6
  8. package/dist/adapters/global/qcoder.js +4 -1
  9. package/dist/adapters/ide/cline.js +3 -0
  10. package/dist/adapters/ide/cursor.js +3 -0
  11. package/dist/adapters/ide/installer.js +17 -5
  12. package/dist/adapters/ide/windsurf.js +3 -0
  13. package/dist/adapters/installedHosts.js +51 -0
  14. package/dist/cli/commands/doctor.js +155 -0
  15. package/dist/cli/commands/entities.js +27 -0
  16. package/dist/cli/commands/history.js +23 -0
  17. package/dist/cli/commands/ingestHook.js +4 -78
  18. package/dist/cli/commands/install.js +34 -56
  19. package/dist/cli/commands/scan.js +16 -0
  20. package/dist/cli/commands/search.js +1 -0
  21. package/dist/cli/commands/team.js +7 -3
  22. package/dist/cli/commands/ui.js +67 -0
  23. package/dist/cli/commands/watch.js +48 -0
  24. package/dist/cli/index.js +44 -1
  25. package/dist/cli/projectId.js +1 -2
  26. package/dist/cli/transcriptSweep.js +142 -0
  27. package/dist/cli/transcripts/claudeCode.js +62 -0
  28. package/dist/cli/transcripts/codex.js +111 -0
  29. package/dist/cli/transcripts/cursor.js +295 -0
  30. package/dist/cli/transcripts/index.js +118 -0
  31. package/dist/cli/transcripts/types.js +15 -0
  32. package/dist/core/api/contracts.js +4 -0
  33. package/dist/core/api/memoryCoreService.js +9 -1
  34. package/dist/core/api/sessionLifecycleService.js +13 -1
  35. package/dist/core/capture/toolEvent.js +94 -0
  36. package/dist/core/extract/factClaims.js +97 -0
  37. package/dist/core/extract/knowledge.js +127 -0
  38. package/dist/core/retrieve/retrieveMemories.js +20 -4
  39. package/dist/core/schema/migrations/012_transcript_checkpoints.sql +4 -0
  40. package/dist/core/schema/migrations/013_fts_porter.sql +32 -0
  41. package/dist/core/schema/migrations/014_memory_entities.sql +19 -0
  42. package/dist/core/schema/migrations/015_memory_supersessions.sql +41 -0
  43. package/dist/core/schema/runMigrations.js +46 -0
  44. package/dist/core/storage/knowledgeRepo.js +67 -0
  45. package/dist/core/storage/memoryRepo.js +45 -12
  46. package/dist/core/storage/memorySearchRepo.js +30 -20
  47. package/dist/core/storage/sessionEventsRepo.js +12 -3
  48. package/dist/core/storage/supersessionRepo.js +101 -0
  49. package/dist/core/summarize/summaryShape.js +1 -1
  50. package/dist/ui/assets.js +181 -0
  51. package/dist/ui/server.js +147 -0
  52. package/docs/REGISTRY_LISTING.md +88 -0
  53. package/docs/architecture.md +27 -3
  54. package/docs/benchmark.md +105 -33
  55. package/docs/migration.md +2 -0
  56. package/docs/troubleshooting.md +52 -4
  57. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,59 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.6.0] - 2026-10-18
9
+
10
+ ### Added
11
+
12
+ - Multi-host transcript imports: `sessionmem scan` and `sessionmem watch` now
13
+ sweep Claude Code, Codex CLI, and Cursor histories by default, with a
14
+ separate parser per host and an explicit unsupported-format error when a
15
+ claimed host's records cannot be parsed. `--host` selects one host.
16
+ ([#52], [#53])
17
+ - Codex CLI support: dated rollout discovery, project and session identity from
18
+ session metadata, resume-file grouping with cross-file deduplication,
19
+ sub-agent session separation, native tool-call events, and filtering of
20
+ encrypted agent payloads and injected machine blocks. ([#52])
21
+ - Cursor support: chat history read from Cursor's SQLite storage through
22
+ private snapshot copies opened read-only, covering both composer index
23
+ layouts, with per-composer checkpoints and live-update handling. ([#53])
24
+ - `sessionmem doctor` now checks transcript discovery for each supported
25
+ host. ([#52], [#53])
26
+
27
+ ## [1.5.0] - 2026-10-18
28
+
29
+ ### Added
30
+
31
+ - `sessionmem scan` to import existing Claude Code transcripts into memory, and
32
+ `sessionmem watch` to capture new and updated sessions continuously. Both are
33
+ idempotent across reruns and restarts and work fully offline. ([#45])
34
+ - Automatic host detection during `sessionmem install`, and a `sessionmem doctor`
35
+ command that checks the store, hooks, adapter wiring, and recall with fix
36
+ hints for each failure. ([#46])
37
+ - Deterministic entity and relation extraction during capture and scan, a
38
+ `sessionmem entities` command, and entity overlap as a recall ranking
39
+ signal. ([#48])
40
+ - Deterministic supersession handling: memories that replace an earlier fact are
41
+ linked with history, and recall prefers current facts while replaced ones stay
42
+ retrievable explicitly. ([#49])
43
+ - `sessionmem ui`, a local read-only web viewer for memories, entities,
44
+ relations, and supersession history, served on localhost with no external
45
+ services. ([#50])
46
+ - COMPARISON.md, a cited comparison with claude-mem, Mem0, and Zep, plus a draft
47
+ MCP registry listing. ([#51])
48
+ - A reproducible offline LoCoMo evaluation: committed corpus with provenance and
49
+ checksums, and a runner reporting hit rate, precision, recall, and latency
50
+ with all network access blocked. ([#44])
51
+
52
+ ### Changed
53
+
54
+ - Retrieval now ranks with SQLite FTS5 BM25 and Porter stemming over memory
55
+ content and summaries. Measured on the committed LoCoMo evaluation, top-5 hit
56
+ rate improved from 1.44% to 61.95%, and to 63.92% with entity overlap. ([#47], [#48])
57
+ - The release workflow skips the npm publish when the version already exists on
58
+ the registry and creates the GitHub release with notes from this
59
+ changelog. ([#43])
60
+
8
61
  ## [1.4.0] - 2026-10-16
9
62
 
10
63
  ### Added
@@ -68,6 +121,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
68
121
  errors and findings fail the job; added scanner dependency updates and
69
122
  Dependabot cooldowns. ([#32])
70
123
 
124
+ [1.6.0]: https://github.com/catfish-1234/sessionmem/compare/v1.5.0...v1.6.0
125
+ [1.5.0]: https://github.com/catfish-1234/sessionmem/compare/679b8954f0b49f197985c40da47f181c00060cd9...v1.5.0
126
+ [#43]: https://github.com/catfish-1234/sessionmem/pull/43
127
+ [#44]: https://github.com/catfish-1234/sessionmem/pull/44
128
+ [#45]: https://github.com/catfish-1234/sessionmem/pull/45
129
+ [#46]: https://github.com/catfish-1234/sessionmem/pull/46
130
+ [#47]: https://github.com/catfish-1234/sessionmem/pull/47
131
+ [#48]: https://github.com/catfish-1234/sessionmem/pull/48
132
+ [#49]: https://github.com/catfish-1234/sessionmem/pull/49
133
+ [#50]: https://github.com/catfish-1234/sessionmem/pull/50
134
+ [#51]: https://github.com/catfish-1234/sessionmem/pull/51
135
+ [#52]: https://github.com/catfish-1234/sessionmem/pull/52
136
+ [#53]: https://github.com/catfish-1234/sessionmem/pull/53
71
137
  [1.4.0]: https://github.com/catfish-1234/sessionmem/compare/a73f503123b1d33c35407ce66403f281ba3451ac...679b8954f0b49f197985c40da47f181c00060cd9
72
138
  [#22]: https://github.com/catfish-1234/sessionmem/pull/22
73
139
  [#23]: https://github.com/catfish-1234/sessionmem/pull/23
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  <a href="https://github.com/catfish-1234/sessionmem/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
10
10
  </p>
11
11
 
12
- **Project memory between sessions, stored on your machine. Startup context used 85.6% fewer tokens than full session history in the included benchmark.**
12
+ **Project memory between sessions, stored on your machine.**
13
13
 
14
14
  ```
15
15
  New session. Claude starts fresh.
@@ -49,16 +49,25 @@ Run this in your project folder after configuring your AI tool (Claude Code, Cur
49
49
 
50
50
  ```bash
51
51
  sessionmem install
52
- # From a plain terminal, choose the host explicitly when needed:
52
+ # Optional: select one host explicitly, including in automation:
53
53
  sessionmem install --adapter claude-code
54
54
  # Other names: cursor, cline, codex, windsurf, antigravity, qcoder, generic
55
55
  ```
56
56
 
57
- This initializes the database and, for supported adapters:
57
+ `install` detects installed hosts from local executables, including from a plain
58
+ terminal, and configures every host found. Automatic discovery currently supports
59
+ Claude Code on `PATH` or in its standard native install locations. Use
60
+ `--adapter <name>` for the other adapters or to override discovery. The existing
61
+ `SESSIONMEM_ADAPTER` environment override also works; the flag takes precedence.
62
+ If no supported host is found, the command says so and exits nonzero before
63
+ creating files. Discovery and configuration work fully offline.
64
+
65
+ This initializes the database and, for each selected adapter:
58
66
 
59
67
  - Registers `sessionmem` with your AI tool's MCP host so the host can start it automatically.
60
68
  - Creates `~/.sessionmem/config.json` with secret redaction enabled and a 90-day retention window, if the file doesn't already exist.
61
69
  - Adds instructions to the selected host's guidance file, such as `~/.claude/CLAUDE.md` for Claude Code or `~/.codex/AGENTS.md` for Codex. You can re-run the command without duplicating those instructions.
70
+ - Installs Claude Code's SessionStart, SessionEnd, and PostToolUse hooks and prints what was configured per host. If a host fails, the remaining hosts are still attempted and the command exits nonzero.
62
71
 
63
72
  ### 3. Restart your AI tool
64
73
 
@@ -73,10 +82,12 @@ After restarting:
73
82
  - In Claude Code, the installed PostToolUse, SessionEnd, and SessionStart hooks capture supported tool events, summarize them, and load prior memories automatically.
74
83
  - Other MCP hosts use sessionmem's tools and installed instructions. Automatic capture and session lifecycle calls depend on the host or agent. MCP registration alone does not install Claude Code hooks there.
75
84
 
76
- Check that the local database is accessible:
85
+ Check the installation offline:
77
86
 
78
87
  ```bash
79
- sessionmem ping
88
+ sessionmem doctor
89
+ # For an explicitly selected host:
90
+ sessionmem doctor --adapter claude-code
80
91
  ```
81
92
 
82
93
  ---
@@ -139,36 +150,21 @@ The SQLite file is easy to inspect, back up, or delete, and you can use it acros
139
150
 
140
151
  ## Benchmark results
141
152
 
142
- These numbers come from `npm run benchmark` (`scripts/benchmark.mjs`). It runs the production retrieval and injection code against fixed, synthetic test data without network calls. See [`docs/benchmark.md`](docs/benchmark.md) for the full report and reproduction steps.
143
-
144
- ### Token savings
145
-
146
- > **~85.6% reduction** in tokens compared to carrying full session history.
147
-
148
- | | Tokens |
149
- |---|---|
150
- | Full session history (baseline) | 1,587 |
151
- | What sessionmem injects at the start of your next session | 228 |
152
-
153
- In this benchmark, about 1,600 tokens of session history become roughly 230 tokens of decisions, warnings, and facts for the next session.
154
-
155
- ### Retrieval accuracy
153
+ `npm run benchmark` evaluates conversational retrieval on the committed LoCoMo
154
+ corpus: 10 conversations, 272 sessions, 5,882 turns, and 1,527 scored questions.
155
+ It captures dialogue through the public `storeMemory` API and asks the original
156
+ questions through `retrieveMemories`, fully offline with no API keys.
156
157
 
157
- > **100% hit-rate**: all 10 test queries retrieved the expected memory.
158
-
159
- | Metric | Result |
160
- |---|---|
161
- | Hit-rate (10 curated queries) | 100.0% |
162
- | Recall | 100.0% |
163
- | Precision | 33.3% |
164
-
165
- Each query returns 3 candidate memories, with one scored as the expected match. That gives 33.3% precision even when the expected memory is found every time. The other two candidates may provide context, but are not scored as matches. These results apply to the 10 curated queries; see the report for the embedding model's limits.
166
-
167
- The benchmark is deterministic and reproducible:
158
+ The evaluation reports hit rate, precision, recall, complete-evidence retrieval,
159
+ and latency. Production retrieval uses offline BM25 ranking with English stemming;
160
+ conversational questions test recall beyond exact copies of memory text.
161
+ See [benchmark results](docs/benchmark.md) and the
162
+ [methodology, dataset license, and metric definitions](benchmarks/README.md).
168
163
 
169
164
  ```bash
170
- npm run build # benchmark imports the compiled code from dist/
171
- npm run benchmark # regenerates docs/benchmark.md
165
+ npm run build
166
+ npm run benchmark
167
+ npm run benchmark -- --output benchmarks/results/locomo.json
172
168
  ```
173
169
 
174
170
  ---
@@ -213,16 +209,22 @@ See [`docs/architecture.md`](docs/architecture.md) for the technical overview.
213
209
 
214
210
  | Command | What it does |
215
211
  |---------|--------------|
216
- | `sessionmem install [--adapter <name>]` | Register sessionmem with the current MCP host and write default config. |
212
+ | `sessionmem install [--adapter <name>]` | Detect installed hosts, register sessionmem with each, and write default config; the flag selects one host. |
213
+ | `sessionmem doctor [--adapter <name>]` | Check the store, schema, transcript discovery, hooks, MCP wiring, and canary recall offline; print PASS/FAIL and repair hints. |
217
214
  | `sessionmem uninstall [--adapter <name>] [--purge]` | Remove sessionmem from the host. `--purge` also deletes the local database. |
218
215
  | `sessionmem run` | Start the MCP server. |
216
+ | `sessionmem ui [--port N]` | Open the local read-only web viewer (default port 3777). |
219
217
  | `sessionmem ping` | Check the local version and database reachability. |
220
- | `sessionmem search <query> [--limit <n>]` | Search memories by semantic query (limit 1–100, default 10). |
218
+ | `sessionmem search <query> [--limit <n>] [--include-history]` | Search current memories by text query (limit 1–100, default 10). Include superseded facts with `--include-history`. |
221
219
  | `sessionmem list` | List up to 200 recent memories for the current project; warns when truncated. |
220
+ | `sessionmem entities [--project <id>] [--json]` | List extracted entities, typed relations, and their source memory IDs. Defaults to the current project. |
221
+ | `sessionmem history [--project <id>] [--json]` | List supersession chains with old/new values, source memory IDs, reasons, and timestamps. |
222
222
  | `sessionmem show <id>` | Show memory details (content capped at 2000 characters; use export for full content). |
223
223
  | `sessionmem forget <id> [--force]` | Delete a memory by ID. |
224
224
  | `sessionmem export [path]` | Export all current-project memories to JSON (default `~/.sessionmem/export-YYYY-MM-DD.json`). |
225
225
  | `sessionmem import <path> [--merge]` | Import up to 1000 valid records from UTF-8 JSON into the current project. Existing IDs are preserved, including with `--merge`; invalid records are skipped with warnings. |
226
+ | `sessionmem scan [--host <host>] [--transcripts-dir <path>]` | Import existing Claude Code, Codex CLI, and Cursor transcripts into local session memories. Reports files found, sessions imported, memories created, skips, and errors. |
227
+ | `sessionmem watch [--once] [--interval <seconds>] [--host <host>] [--transcripts-dir <path>]` | Capture new or updated Claude Code, Codex CLI, and Cursor transcripts. Polls every 5 seconds by default; `--once` runs a single sweep. |
226
228
  | `sessionmem stats` | Show memory statistics for the current project. |
227
229
  | `sessionmem savings [--json]` | Show token savings from compression and injection, with percentage. |
228
230
  | `sessionmem redact-scan [--apply]` | Scan stored memories for secrets; `--apply` redacts in place. |
@@ -253,9 +255,185 @@ for Windows paths. Imports accept CRLF and a UTF-8 BOM; convert other encodings
253
255
 
254
256
  `SESSIONMEM_PROJECT_ID` pins the project bucket (useful when moving a repository);
255
257
  otherwise it is derived from the repository root or working directory.
256
- `SESSIONMEM_ADAPTER` selects the install/uninstall host unless `--adapter` is set.
258
+ `SESSIONMEM_ADAPTER` selects the install/doctor/uninstall host unless `--adapter` is set.
257
259
  `SESSIONMEM_DEBUG=1` enables ingest-hook diagnostics on stderr.
258
260
 
261
+ ### Inspect memories in your browser
262
+
263
+ ```bash
264
+ sessionmem ui
265
+ sessionmem ui --port 8080
266
+ sessionmem ui --port 0 # Pick an available port
267
+ ```
268
+
269
+ The command prints its URL and opens your default browser. It binds only to
270
+ `127.0.0.1`, needs no authentication, and serves all assets locally using Node's
271
+ built-in HTTP server. No external services, CDNs, or additional runtime dependencies
272
+ are used. If browser launch is unavailable, open the printed URL manually. Press
273
+ Ctrl-C to stop the server and close its database connection.
274
+
275
+ The viewer starts on the current project, with a selector for other projects in
276
+ the same store. Browse saved memories, search through the existing recall path,
277
+ inspect extracted entities and relations with their source memories, and follow
278
+ supersession chains with old/new values, source IDs, assertion times, detection
279
+ times, and partial/full status. Search returns up to 50 results and defaults to
280
+ current facts; select **Include superseded facts in search** to recall history.
281
+ Browsing lists all saved records, including superseded and expired ones, in pages
282
+ of 50. Source IDs open full memory details. Entity visibility and history links
283
+ use the same rules as `sessionmem entities` and `sessionmem history`.
284
+
285
+ The database is opened read-only. Browsing and on-demand search do not increment
286
+ access counts, migrate the schema, or change memories. Only HTTP GET and HEAD are
287
+ accepted; no write, delete, or edit routes are exposed. Responses are not cached,
288
+ and cross-origin browser requests and unexpected Host headers are rejected. Run
289
+ `sessionmem ping` first to initialize a missing store or upgrade an older schema.
290
+ The usual `SESSIONMEM_DB_PATH` and `SESSIONMEM_PROJECT_ID` overrides
291
+ apply. Anyone with access to this computer can read the local viewer while it is
292
+ running; stop it when finished inspecting private memories.
293
+
294
+ ### Import existing sessions and watch for new ones
295
+
296
+ When a fact changes, sessionmem can link its replacement locally. For example, “I use VS Code” followed by “I switched to Neovim” supersedes the earlier editor choice. Recall excludes fully superseded memories by default. `retrieveMemories` accepts the optional `includeHistory: true`; `sessionmem search --include-history` searches earlier facts too. `sessionmem history` shows what replaced what. The old memory stays available through show, list, and export.
297
+
298
+ Detection uses deterministic entity/relation rules, shared purpose words, a small tool-category vocabulary, and explicit negation or replacement phrases. It needs no API or model. Unfamiliar wording can be missed, and ambiguous changes can be misclassified. A memory with independent claims remains searchable while any claim is current; history labels these partial supersessions. See [supersession details](docs/architecture.md#contradiction-and-supersession).
299
+
300
+ Run a scan to capture Claude Code, Codex CLI, and Cursor sessions from before sessionmem was installed:
301
+
302
+ ```bash
303
+ sessionmem scan
304
+ sessionmem watch --once
305
+ sessionmem watch --interval 10
306
+ sessionmem scan --host cursor
307
+ ```
308
+
309
+ Both commands sweep all detected transcript histories by default:
310
+
311
+ - Claude Code: `~/.claude/projects/**/*.jsonl`.
312
+ - Codex CLI: `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl`.
313
+ - Cursor: `User/globalStorage/state.vscdb` under
314
+ `~/Library/Application Support/Cursor` on macOS, `~/.config/Cursor` on Linux,
315
+ or `%APPDATA%/Cursor` on Windows.
316
+
317
+ Missing history directories are allowed. Use `--transcripts-dir "/path/to/history"`
318
+ to select another transcript tree; each JSONL file's host is detected from its records.
319
+ For Cursor, the override accepts its `User` directory or a `state.vscdb` path.
320
+ Use `--host cursor`, `--host codex`, or `--host claude-code` to select one host,
321
+ including when using an override. A Cursor User directory or `.vscdb` file is
322
+ also detected without `--host`. Files that do not match a supported format, or
323
+ the selected host, produce an explicit `unsupported format` error.
324
+
325
+ Both commands work offline without API keys and always use the local summarizer,
326
+ even if cloud summarization is enabled elsewhere. `sessionmem doctor` checks
327
+ transcript discovery for all three hosts, parses JSONL samples, and validates
328
+ Cursor's composer indexes and message rows when history is present.
329
+
330
+ Transcript user/assistant text, supported Claude tool uses (Bash, Edit, Write,
331
+ MultiEdit), and Codex function calls pass through the existing event-ingestion,
332
+ redaction, and session-summary pipeline. Codex tool events retain the native tool
333
+ name and `call_id`. Encrypted `agent_message` payloads and obvious injected machine
334
+ blocks, including plugin catalogues and environment context, are excluded. Each
335
+ session gets one summary memory, including short sessions with only one event.
336
+ Updated sessions refresh that summary. Project buckets come from
337
+ each Claude transcript's `cwd`, Codex `session_meta.payload.cwd`, or Cursor's
338
+ workspace path, using the repository root when available; the directory where you run scan/watch does not
339
+ select the project. `SESSIONMEM_PROJECT_ID` can explicitly pin all imports to one
340
+ bucket. Transcripts without capturable events are skipped. Claude transcripts
341
+ without a working directory are also skipped; Codex session metadata must contain
342
+ an ID and working directory. Nested subagent files get separate session summaries.
343
+ Summary memories record `sourceAdapter` as `claude-code`, `codex`, or `cursor`
344
+ according to the transcript host. Cursor session IDs come from `composerId`.
345
+
346
+ Cursor stores messages in `cursorDiskKV` bubble rows in its global SQLite
347
+ database. The importer supports workspace `composer.composerData` indexes
348
+ (Cursor 2.6 and earlier) and the central `composer.composerHeaders` index
349
+ (Cursor 3.0 and later). It captures up to 4,000 characters of user/assistant text
350
+ per bubble and excludes tool-only bubbles and internal tool/code fields. Missing
351
+ tables, index keys, or required conversation data produce an explicit
352
+ `unsupported format for cursor` error.
353
+
354
+ Cursor databases are never written. Each sweep copies the database and its WAL
355
+ to a private temporary directory, checking for concurrent changes during the
356
+ copy. SQLite opens the copy read-only, using immutable mode when URI support is
357
+ available and there is no WAL to replay. Cursor's live database is never opened by SQLite. Busy
358
+ copies are reported and retried. Each sweep reads a fresh snapshot; per-composer
359
+ checkpoints skip unchanged messages, and streamed bubble updates refresh the
360
+ existing event without duplicating it.
361
+
362
+ Codex resumes create new rollout files. Files with the same session ID are grouped,
363
+ and replayed events are deduplicated across files, sweeps, and restarts. A Codex
364
+ sub-agent keeps its own `session_meta.payload.id`, even when `parent_thread_id`
365
+ points to a parent session.
366
+
367
+ Re-running either command does not duplicate memories or transcript events.
368
+ File and composer checkpoints live in the selected memory database, and unchanged
369
+ files or composers are skipped across restarts. For Cursor, `files found` counts
370
+ global databases, and `skipped` counts unchanged or empty composers.
371
+ Stable event IDs also make interrupted imports safe to
372
+ retry. Partial or malformed JSONL lines are reported and retried on later sweeps;
373
+ valid events in the same file can still be captured. A single sweep exits with
374
+ status 1 if it encounters errors. The daemon keeps running and retries errors.
375
+
376
+ `watch` prints its first sweep and subsequent imports or errors. It runs in the
377
+ foreground until SIGINT or SIGTERM. For a background process:
378
+
379
+ ```bash
380
+ nohup sessionmem watch > sessionmem-watch.log 2>&1 &
381
+ ```
382
+
383
+ For macOS launchd, save this as
384
+ `~/Library/LaunchAgents/io.sessionmem.watch.plist`. Replace the executable path
385
+ with the output of `command -v sessionmem`, and adjust PATH for your Node install:
386
+
387
+ ```xml
388
+ <?xml version="1.0" encoding="UTF-8"?>
389
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
390
+ <plist version="1.0">
391
+ <dict>
392
+ <key>Label</key><string>io.sessionmem.watch</string>
393
+ <key>ProgramArguments</key>
394
+ <array><string>/opt/homebrew/bin/sessionmem</string><string>watch</string></array>
395
+ <key>EnvironmentVariables</key>
396
+ <dict><key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string></dict>
397
+ <key>RunAtLoad</key><true/>
398
+ <key>KeepAlive</key><true/>
399
+ </dict>
400
+ </plist>
401
+ ```
402
+
403
+ ```bash
404
+ launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/io.sessionmem.watch.plist
405
+ # Stop and unload:
406
+ launchctl bootout "gui/$(id -u)/io.sessionmem.watch"
407
+ ```
408
+
409
+ For a Linux systemd user service, save this as
410
+ `~/.config/systemd/user/sessionmem-watch.service`. Replace the executable path
411
+ and adjust PATH for your Node install:
412
+
413
+ ```ini
414
+ [Unit]
415
+ Description=Capture Claude Code, Codex, and Cursor transcripts with sessionmem
416
+
417
+ [Service]
418
+ ExecStart=/usr/local/bin/sessionmem watch
419
+ Environment=PATH=/usr/local/bin:/usr/bin:/bin
420
+ Restart=on-failure
421
+ RestartSec=5
422
+
423
+ [Install]
424
+ WantedBy=default.target
425
+ ```
426
+
427
+ ```bash
428
+ systemctl --user daemon-reload
429
+ systemctl --user enable --now sessionmem-watch.service
430
+ journalctl --user -u sessionmem-watch.service -f
431
+ # Stop and disable:
432
+ systemctl --user disable --now sessionmem-watch.service
433
+ ```
434
+
435
+ These are optional examples; `sessionmem install` does not register a daemon.
436
+
259
437
  ---
260
438
 
261
439
  ## Privacy, secrets, and your data
@@ -296,13 +474,13 @@ Old decisions and duplicate facts can accumulate in a memory store, causing the
296
474
  sessionmem retention prune --force # actually deletes
297
475
  ```
298
476
 
299
- 2. **Importance-weighted ranking**: retrieval ranks memories by semantic relevance, recency, and importance. Older, less important notes rank lower even before they are pruned.
477
+ 2. **Importance-weighted ranking**: retrieval ranks memories by offline BM25 lexical relevance, entity overlap, recency, and importance. Older, less important notes rank lower even before they are pruned.
300
478
 
301
479
  3. **Token-budgeted injection**: only the highest-ranked memories are loaded, trimmed to a small token budget (see [benchmarks](#benchmark-results)). The context stays within that budget as the store grows.
302
480
 
303
481
  4. **Conflict resolution in team mode**: merging teammate memories uses last-write-wins by id to avoid stale duplicates and keeps the higher importance score so a warning isn't downgraded.
304
482
 
305
- The benchmark above found every expected memory across its 10 queries (100% hit-rate and recall) with ranking and trimming enabled.
483
+ The [conversational benchmark](benchmarks/README.md) measures retrieval quality separately from retention and startup token budgets.
306
484
 
307
485
  Export your memories before pruning if you want to keep a copy:
308
486
 
@@ -359,7 +537,7 @@ Other tools that implement the [Model Context Protocol](https://modelcontextprot
359
537
  ## Further documentation
360
538
 
361
539
  - [Architecture](docs/architecture.md): how the core engine, adapters, CLI, and SQLite storage fit together.
362
- - [Benchmark](docs/benchmark.md): full token-reduction and retrieval-accuracy report, and how to reproduce it.
540
+ - [Benchmark](docs/benchmark.md): conversational retrieval results and reproduction steps.
363
541
  - [Privacy and retention](docs/privacy-and-retention.md): secret redaction, retention pruning, and config.
364
542
  - [Team mode](docs/team-mode.md): shared-path team memory.
365
543
  - [Cloud summarization](docs/cloud-summarization.md): the opt-in cloud summarization path.
@@ -372,13 +550,27 @@ Other tools that implement the [Model Context Protocol](https://modelcontextprot
372
550
 
373
551
  For install failures, adapter issues, missing session data, or `better-sqlite3` native-build problems, see [`docs/troubleshooting.md`](docs/troubleshooting.md).
374
552
 
375
- To refresh the installation and check for stored session data:
553
+ Start with offline diagnostics:
376
554
 
377
555
  ```bash
378
- sessionmem install # idempotent; Claude Code also installs all three hooks
379
- sessionmem stats # memories, sessions, and session_events for the current project
556
+ sessionmem doctor
557
+ sessionmem doctor --adapter claude-code # explicit host override
380
558
  ```
381
559
 
560
+ `doctor` reports a separate PASS or FAIL for the memory store, schema, transcript
561
+ discovery for Claude Code and Codex, hook definitions shipped with the current
562
+ version, configured MCP command, and a
563
+ canary write/recall. It exits nonzero if any check fails and prints fix hints,
564
+ including exact commands for safe repairs. It does not create a missing store or
565
+ apply migrations. Its temporary canary is isolated from your projects and rolled
566
+ back even on failure. The MCP check starts the installed local command and verifies
567
+ its version, tools, and a stats response; it does not require a running agent.
568
+
569
+ Use `sessionmem ping` to initialize a missing store or apply pending schema
570
+ migrations, or `sessionmem install --adapter claude-code` to refresh Claude Code's
571
+ MCP registration and hooks. Run repairs with the same `SESSIONMEM_DB_PATH` as
572
+ doctor when using a custom store.
573
+
382
574
  If `stats` shows `sessions: 0` after real work, see [“0 sessions” / no session data recorded](docs/troubleshooting.md#0-sessions--no-session-data-recorded). Memories are keyed to the **repository root**, so every directory inside one repo shares a bucket; outside a repo the working directory itself is the key.
383
575
 
384
576
  To check database access and memory counts:
@@ -0,0 +1,109 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
5
+ import { IDEInstaller, isManagedClaudeHook } from "./ide/installer.js";
6
+ import { resolveDbPath } from "../cli/context.js";
7
+ function object(value) {
8
+ return value !== null && typeof value === "object" && !Array.isArray(value)
9
+ ? value
10
+ : {};
11
+ }
12
+ export function checkHooks(installation) {
13
+ const expected = installation.hooks;
14
+ if (!expected)
15
+ return "This host uses MCP tools; no lifecycle hooks are required";
16
+ const config = IDEInstaller.parseJsonc(readFileSync(expected.path, "utf8"));
17
+ if (config.disableAllHooks === true)
18
+ throw new Error("Claude Code has disableAllHooks enabled");
19
+ const hooks = object(config.hooks);
20
+ for (const spec of expected.entries) {
21
+ const entries = Array.isArray(hooks[spec.event]) ? hooks[spec.event] : [];
22
+ const managed = entries.flatMap((value) => {
23
+ const entry = object(value);
24
+ return Array.isArray(entry.hooks) ? entry.hooks.filter((value) => isManagedClaudeHook(object(value).command, spec.command)).map((hook) => ({ entry, hook: object(hook) })) : [];
25
+ });
26
+ const matches = managed.filter(({ entry, hook }) => {
27
+ if (entry.matcher !== spec.matcher || entry.async === true)
28
+ return false;
29
+ return hook.type === "command" && hook.command === spec.command && hook.async !== true;
30
+ });
31
+ if (managed.length !== 1 || matches.length !== 1)
32
+ throw new Error(`${spec.event} hook is missing, duplicated, or differs from the current definition`);
33
+ }
34
+ return `SessionStart, SessionEnd, PostToolUse match current hook definitions (${expected.path})`;
35
+ }
36
+ /** Check the installed launch entry before executing the known local command. */
37
+ export function readMcpLaunch(installation) {
38
+ const expected = installation.mcp;
39
+ const content = readFileSync(expected.path, "utf8");
40
+ let entry;
41
+ if (expected.format === "toml") {
42
+ // The Codex installer writes this small TOML section with JSON-compatible
43
+ // quoted values. Refuse unfamiliar syntax rather than claiming it works.
44
+ const section = content.match(/^\s*\[mcp_servers\.sessionmem\][^\S\r\n]*\r?\n([\s\S]*?)(?=^\s*\[|$(?![\s\S]))/m)?.[1];
45
+ if (!section)
46
+ throw new Error("sessionmem MCP section is missing");
47
+ if (/^\s*\[mcp_servers\.sessionmem\./m.test(content) || /^\s*(?:env|env_vars|cwd|url)\s*=/m.test(section)) {
48
+ throw new Error("Custom Codex launch settings require manual verification; doctor supports the installer-generated command and args");
49
+ }
50
+ if (/^\s*enabled\s*=\s*false\s*$/m.test(section))
51
+ throw new Error("sessionmem MCP server is disabled");
52
+ entry = {
53
+ command: JSON.parse(section.match(/^\s*command\s*=\s*(.+)$/m)?.[1] ?? "null"),
54
+ args: JSON.parse(section.match(/^\s*args\s*=\s*(.+)$/m)?.[1] ?? "null"),
55
+ };
56
+ }
57
+ else {
58
+ const config = IDEInstaller.parseJsonc(content);
59
+ entry = object(object(config.mcpServers).sessionmem);
60
+ }
61
+ if (entry.disabled === true)
62
+ throw new Error("sessionmem MCP server is disabled");
63
+ if (entry.type !== undefined && entry.type !== "stdio")
64
+ throw new Error("sessionmem MCP server must use the local stdio transport");
65
+ if (entry.command !== expected.command || JSON.stringify(entry.args) !== JSON.stringify(expected.args)) {
66
+ throw new Error(`sessionmem MCP launch entry differs from the current definition (${expected.path})`);
67
+ }
68
+ if (entry.env !== undefined && (entry.env === null || typeof entry.env !== "object" || Array.isArray(entry.env))) {
69
+ throw new Error("sessionmem MCP environment must be an object");
70
+ }
71
+ const env = object(entry.env);
72
+ if (Object.values(env).some((value) => typeof value !== "string")) {
73
+ throw new Error("sessionmem MCP environment values must be strings");
74
+ }
75
+ return { command: expected.command, args: expected.args, env: env };
76
+ }
77
+ export async function probeMcpWiring(launch, dbPath, version) {
78
+ if (launch.env?.SESSIONMEM_DB_PATH && resolve(resolveDbPath(launch.env.SESSIONMEM_DB_PATH)) !== resolve(dbPath)) {
79
+ throw new Error("MCP configuration selects a different SESSIONMEM_DB_PATH; run doctor with that path");
80
+ }
81
+ const inherited = Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== undefined));
82
+ const client = new Client({ name: "sessionmem-doctor", version });
83
+ const transport = new StdioClientTransport({
84
+ command: launch.command,
85
+ args: launch.args,
86
+ env: { ...inherited, ...launch.env, SESSIONMEM_DB_PATH: dbPath },
87
+ stderr: "ignore",
88
+ });
89
+ try {
90
+ await client.connect(transport, { timeout: 5000 });
91
+ const server = client.getServerVersion();
92
+ if (server?.name !== "sessionmem" || server.version !== version) {
93
+ throw new Error(`MCP server version does not match sessionmem ${version}`);
94
+ }
95
+ const tools = await client.listTools({}, { timeout: 5000 });
96
+ for (const name of ["stats", "storeMemory", "retrieveMemories"]) {
97
+ if (!tools.tools.some((tool) => tool.name === name))
98
+ throw new Error(`MCP tool ${name} is missing`);
99
+ }
100
+ const result = await client.callTool({ name: "stats", arguments: {} }, undefined, { timeout: 5000 });
101
+ const payload = Array.isArray(result.content) ? object(result.content.find((part) => object(part).type === "text")) : {};
102
+ if (result.isError || typeof payload.text !== "string" || !JSON.parse(payload.text).ok) {
103
+ throw new Error("MCP stats tool failed");
104
+ }
105
+ }
106
+ finally {
107
+ await transport.close();
108
+ }
109
+ }
@@ -69,6 +69,7 @@ const TOOL_DEFINITIONS = [
69
69
  mode: retrieveMemoriesRequestSchema.shape.mode.describe("'auto' for the standard startup context-load path. 'on-demand' for an explicit mid-session retrieval triggered by a specific task or question."),
70
70
  depth: retrieveMemoriesRequestSchema.shape.depth.describe("'default' for standard semantic search. 'deep' for a broader sweep that surfaces less-similar memories — use when the topic is new or unfamiliar."),
71
71
  tags: retrieveMemoriesRequestSchema.shape.tags.describe("Optional tag filter. When provided, only return memories that have ALL listed tags."),
72
+ includeHistory: retrieveMemoriesRequestSchema.shape.includeHistory.describe("Include superseded facts for historical questions. Omit for current facts only."),
72
73
  },
73
74
  },
74
75
  {
@@ -209,18 +210,21 @@ export class GenericMCPAdapter {
209
210
  guidanceTargets() {
210
211
  return [join(process.cwd(), "AGENTS.md")];
211
212
  }
213
+ installation() {
214
+ return { mcp: { path: join(process.cwd(), ".mcp.json"), command: "sessionmem", args: ["run"] } };
215
+ }
212
216
  /**
213
217
  * Fallback for hosts that aren't specifically detected: register sessionmem
214
218
  * in a project-local `.mcp.json` (the de-facto generic MCP config format).
215
219
  */
216
220
  async install() {
217
- const configPath = join(process.cwd(), ".mcp.json");
221
+ const configPath = this.installation().mcp.path;
218
222
  return IDEInstaller.injectMcpConfig(configPath, "sessionmem", "sessionmem", [
219
223
  "run",
220
224
  ]);
221
225
  }
222
226
  async uninstall() {
223
- const configPath = join(process.cwd(), ".mcp.json");
227
+ const configPath = this.installation().mcp.path;
224
228
  return IDEInstaller.removeMcpConfig(configPath, "sessionmem");
225
229
  }
226
230
  async call(_method, _request) {
@@ -11,13 +11,16 @@ import { IDEInstaller } from "../ide/installer.js";
11
11
  const ANTIGRAVITY_MCP_CONFIG = [".gemini", "config", "mcp_config.json"];
12
12
  export class AntigravityAdapter extends GenericMCPAdapter {
13
13
  name = "Antigravity";
14
+ installation() {
15
+ return { mcp: { path: join(homedir(), ...ANTIGRAVITY_MCP_CONFIG), command: "sessionmem", args: ["run"] } };
16
+ }
14
17
  // Capabilities inherited from GenericMCPAdapter (tools only).
15
18
  /** Antigravity reads AGENTS.md-style guidance from its global config dir. */
16
19
  guidanceTargets() {
17
20
  return [join(homedir(), ".antigravity", "AGENTS.md")];
18
21
  }
19
22
  async install() {
20
- const configPath = join(homedir(), ...ANTIGRAVITY_MCP_CONFIG);
23
+ const configPath = this.installation().mcp.path;
21
24
  return IDEInstaller.injectMcpConfig(configPath, "sessionmem", "sessionmem", [
22
25
  "run",
23
26
  ]);
@@ -21,15 +21,30 @@ export class ClaudeCodeAdapter extends GenericMCPAdapter {
21
21
  settingsPath() {
22
22
  return join(homedir(), ".claude", "settings.json");
23
23
  }
24
+ installation() {
25
+ const isWindows = process.platform === "win32";
26
+ return {
27
+ mcp: {
28
+ path: join(homedir(), ".claude.json"),
29
+ command: isWindows ? "cmd" : "sessionmem",
30
+ args: isWindows ? ["/c", "sessionmem", "run"] : ["run"],
31
+ },
32
+ hooks: {
33
+ path: this.settingsPath(),
34
+ entries: [
35
+ { event: "SessionStart", command: SESSIONMEM_HOOK_COMMAND },
36
+ { event: "SessionEnd", command: SESSIONMEM_SESSION_END_HOOK_COMMAND },
37
+ { event: "PostToolUse", command: SESSIONMEM_POST_TOOL_HOOK_COMMAND, matcher: SESSIONMEM_POST_TOOL_MATCHER },
38
+ ],
39
+ },
40
+ };
41
+ }
24
42
  async install() {
25
- const configPath = join(homedir(), ".claude.json");
26
43
  // On Windows, the globally-installed `sessionmem` bin is a `.cmd` shim.
27
44
  // Claude Code spawns MCP servers WITHOUT a shell, and `.cmd` shims require
28
45
  // cmd.exe to execute, so a bare `command: "sessionmem"` fails to start.
29
46
  // Route through `cmd /c sessionmem run` so the shim is resolved correctly.
30
- const isWindows = process.platform === "win32";
31
- const command = isWindows ? "cmd" : "sessionmem";
32
- const args = isWindows ? ["/c", "sessionmem", "run"] : ["run"];
47
+ const { path: configPath, command, args } = this.installation().mcp;
33
48
  const mcpOk = await IDEInstaller.injectMcpConfig(configPath, "sessionmem", command, args);
34
49
  // Register a SessionStart hook so prior memories are injected automatically
35
50
  // at the start of EVERY Claude Code session. This is the deterministic