claude-mem-lite 2.33.1 → 2.33.3

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.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "claude-mem-lite",
13
- "version": "2.33.1",
13
+ "version": "2.33.3",
14
14
  "source": "./",
15
15
  "description": "Lightweight persistent memory system for Claude Code — FTS5 search, episode batching, error-triggered recall"
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.33.1",
3
+ "version": "2.33.3",
4
4
  "description": "Lightweight persistent memory system for Claude Code — FTS5 search, episode batching, error-triggered recall",
5
5
  "author": {
6
6
  "name": "sdsrss"
package/hook-handoff.mjs CHANGED
@@ -16,13 +16,21 @@ import * as taskReaderModule from './lib/task-reader.mjs';
16
16
  /**
17
17
  * Build and save a handoff snapshot to session_handoffs table.
18
18
  * Called synchronously during handleStop (/exit) or handleSessionStart (/clear).
19
+ *
20
+ * Dual id: `sessionId` is the mem-internal id that user_prompts / observations
21
+ * were written with (handleUserPrompt uses getSessionId()) — it drives all
22
+ * DB lookups. `scopeSessionId` is the CC UUID from hook stdin used to scope
23
+ * the stored row so parallel CC sessions don't clobber each other. When
24
+ * `scopeSessionId` is null/undefined, `sessionId` is used for both (legacy).
25
+ *
19
26
  * @param {Database} db Opened main database
20
- * @param {string} sessionId Session being handed off
27
+ * @param {string} sessionId Mem-internal session id (query key)
21
28
  * @param {string} project Project identifier
22
29
  * @param {'clear'|'exit'} type Handoff type
23
30
  * @param {object|null} episodeSnapshot Episode buffer captured before flushing
31
+ * @param {string|null} [scopeSessionId=null] CC UUID for session_handoffs.session_id column
24
32
  */
25
- export function buildAndSaveHandoff(db, sessionId, project, type, episodeSnapshot) {
33
+ export function buildAndSaveHandoff(db, sessionId, project, type, episodeSnapshot, scopeSessionId = null) {
26
34
  // 1. Working objective — from user prompts
27
35
  const prompts = db.prepare(`
28
36
  SELECT prompt_text FROM user_prompts
@@ -122,6 +130,9 @@ export function buildAndSaveHandoff(db, sessionId, project, type, episodeSnapsho
122
130
 
123
131
  // UPSERT keyed on (project, type, session_id) — parallel sessions coexist.
124
132
  // Same session re-writing its own handoff (e.g. repeated /clear) updates in place.
133
+ // `scopeSessionId` (CC UUID) tags the row for parallel scoping; falls back to
134
+ // the mem-internal `sessionId` when the caller didn't supply one (tests + legacy).
135
+ const storedSessionId = scopeSessionId || sessionId;
125
136
  db.prepare(`
126
137
  INSERT INTO session_handoffs (project, type, session_id, working_on, completed, unfinished, key_files, key_decisions, match_keywords, created_at_epoch, git_sha_at_handoff)
127
138
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
@@ -135,7 +146,7 @@ export function buildAndSaveHandoff(db, sessionId, project, type, episodeSnapsho
135
146
  created_at_epoch = excluded.created_at_epoch,
136
147
  git_sha_at_handoff = excluded.git_sha_at_handoff
137
148
  `).run(
138
- project, type, sessionId,
149
+ project, type, storedSessionId,
139
150
  truncate(workingOn, 1000),
140
151
  completed.map(c => `[${c.type}] ${c.title}`).join('\n'),
141
152
  unfinished.length > 3000 ? unfinished.slice(0, 2999) + '…' : unfinished,
package/hook.mjs CHANGED
@@ -3,6 +3,20 @@
3
3
  // Selective encoding, episodic batching, error-triggered recall
4
4
  // Hooks (fast <100ms): post-tool-use, session-start, stop
5
5
  // Background workers (slow): llm-episode, llm-summary
6
+ //
7
+ // ─── Session-id invariant (do not violate — see bf121aa / v2.33.2) ──────────
8
+ // Two session identifiers coexist in this codebase:
9
+ // • mem-internal id: `hook-<project>-<hash>`, produced by getSessionId().
10
+ // handleUserPrompt writes it into user_prompts / sdk_sessions.content_session_id
11
+ // / observations.memory_session_id. Treat as the ONLY valid WHERE / JOIN key
12
+ // for those three tables.
13
+ // • CC UUID: `hookData.session_id` from stdin. Use ONLY for
14
+ // session_handoffs.session_id (parallel-session scoping, per bf121aa).
15
+ // Mixing them silently breaks everything — UPDATE matches 0 rows, SELECT returns
16
+ // empty, buildAndSaveHandoff early-returns, no throw. Precedent: v2.33.1 shipped
17
+ // with the two mixed since 2026-04-12; 48 stale 'active' sessions + 0 handoffs
18
+ // for projects--mem went unnoticed for 4 days. Keep the split or document why
19
+ // you're changing it.
6
20
 
7
21
  import { randomUUID } from 'crypto';
8
22
  import { join } from 'path';
@@ -95,7 +109,11 @@ if (!event) process.exit(0);
95
109
 
96
110
  // ─── Episode Flush ──────────────────────────────────────────────────────────
97
111
 
98
- function flushEpisode(episode) {
112
+ // hookEventName defaults to 'PostToolUse' since that's the most common caller.
113
+ // Stop / SessionStart callers MUST pass their own event name — CC rejects
114
+ // hook output whose hookEventName doesn't match the triggering event
115
+ // (regression introduced in v2.33.1's structured receipt, fixed in v2.33.3).
116
+ function flushEpisode(episode, hookEventName = 'PostToolUse') {
99
117
  if (!episode || episode.entries.length === 0) return;
100
118
 
101
119
  // Collect Read file paths tracked by post-tool-use.sh
@@ -160,7 +178,7 @@ function flushEpisode(episode) {
160
178
  process.stdout.write(JSON.stringify({
161
179
  suppressOutput: true,
162
180
  hookSpecificOutput: {
163
- hookEventName: 'PostToolUse',
181
+ hookEventName,
164
182
  additionalContext: lines.join('\n'),
165
183
  },
166
184
  }));
@@ -326,9 +344,12 @@ async function handleStop() {
326
344
  }
327
345
  } catch { /* stdin unavailable — fall back to local session id */ }
328
346
 
329
- // Capture session info BEFORE cleanup. Prefer CC session id (parallel-safe);
330
- // fall back to file-based id for backward compat.
331
- const sessionId = ccSessionId || getSessionId();
347
+ // Capture session info BEFORE cleanup. All DB lookups use the mem-internal id
348
+ // (that's what handleUserPrompt wrote into user_prompts / sdk_sessions / observations
349
+ // via getSessionId()). `ccSessionId` is used only to tag session_handoffs rows
350
+ // for parallel-session scoping — it must not be used as a query key, otherwise
351
+ // queries miss and UPDATE sdk_sessions becomes a no-op (v2.33.2 regression fix).
352
+ const sessionId = getSessionId();
332
353
  const project = inferProject();
333
354
 
334
355
  // Snapshot episode BEFORE flush for handoff extraction
@@ -339,7 +360,7 @@ async function handleStop() {
339
360
  try {
340
361
  const episode = readEpisode();
341
362
  if (episode) {
342
- flushEpisode(episode);
363
+ flushEpisode(episode, 'Stop');
343
364
  }
344
365
  } finally {
345
366
  releaseLock();
@@ -381,8 +402,11 @@ async function handleStop() {
381
402
  UPDATE sdk_sessions SET status = 'completed', completed_at = ?, completed_at_epoch = ?
382
403
  WHERE content_session_id = ? AND status = 'active'
383
404
  `).run(new Date().toISOString(), Date.now(), sessionId);
384
- // Save handoff snapshot for cross-session continuity
385
- try { buildAndSaveHandoff(db, sessionId, project, 'exit', episodeSnapshot); }
405
+ // Save handoff snapshot for cross-session continuity.
406
+ // sessionId = mem-internal (query key); ccSessionId = CC UUID (scope key for
407
+ // parallel-safe row identity). Without the split, CC UUID-based queries miss
408
+ // user_prompts and the handoff row is silently skipped (see hook-handoff.mjs).
409
+ try { buildAndSaveHandoff(db, sessionId, project, 'exit', episodeSnapshot, ccSessionId || sessionId); }
386
410
  catch (e) { debugCatch(e, 'handleStop-handoff'); }
387
411
 
388
412
  // Fast summary baseline — ensures summary exists even if background LLM fails
@@ -489,7 +513,7 @@ async function handleSessionStart() {
489
513
  try {
490
514
  const prevEpisode = readEpisode();
491
515
  if (prevEpisode && prevEpisode.entries && prevEpisode.entries.length > 0) {
492
- flushEpisode(prevEpisode);
516
+ flushEpisode(prevEpisode, 'SessionStart');
493
517
  }
494
518
  } finally {
495
519
  releaseLock();
@@ -664,12 +688,12 @@ async function handleSessionStart() {
664
688
 
665
689
  if (prevSessionId) {
666
690
  // Save handoff for cross-session continuity (/clear or /compact).
667
- // Prefer CC session id (stable across /clear within same CC session, and
668
- // unique across parallel sessions for the same project) so UserPromptSubmit
669
- // can scope by hookData.session_id. Fall back to the mem plugin's file-based
670
- // id for legacy/test paths.
671
- const handoffSessionId = ccSessionId || prevSessionId;
672
- try { buildAndSaveHandoff(db, handoffSessionId, prevProject || project, 'clear', episodeSnapshot); }
691
+ // prevSessionId is the mem-internal id use it to look up the finished session's
692
+ // user_prompts / observations. ccSessionId (same CC session across /clear) scopes
693
+ // the stored row so UserPromptSubmit can read its own handoff back.
694
+ // Legacy/test paths (no stdin) fall back to prevSessionId for both.
695
+ const handoffScopeId = ccSessionId || prevSessionId;
696
+ try { buildAndSaveHandoff(db, prevSessionId, prevProject || project, 'clear', episodeSnapshot, handoffScopeId); }
673
697
  catch (e) { debugCatch(e, 'session-start-handoff'); }
674
698
 
675
699
  // Read the just-saved handoff for downstream consumers (fast summary remaining, working state).
@@ -677,7 +701,7 @@ async function handleSessionStart() {
677
701
  try {
678
702
  prevClearHandoff = db.prepare(
679
703
  'SELECT working_on, unfinished, key_files FROM session_handoffs WHERE project = ? AND type = ? AND session_id = ?'
680
- ).get(prevProject || project, 'clear', handoffSessionId);
704
+ ).get(prevProject || project, 'clear', handoffScopeId);
681
705
  } catch {}
682
706
 
683
707
  // Generate session summary for previous session (background Haiku — richer version)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.33.1",
3
+ "version": "2.33.3",
4
4
  "description": "Lightweight persistent memory system for Claude Code",
5
5
  "type": "module",
6
6
  "engines": {