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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/hook-handoff.mjs +14 -3
- package/hook.mjs +40 -16
- package/package.json +1 -1
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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.
|
|
330
|
-
//
|
|
331
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
668
|
-
//
|
|
669
|
-
//
|
|
670
|
-
//
|
|
671
|
-
const
|
|
672
|
-
try { buildAndSaveHandoff(db,
|
|
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',
|
|
704
|
+
).get(prevProject || project, 'clear', handoffScopeId);
|
|
681
705
|
} catch {}
|
|
682
706
|
|
|
683
707
|
// Generate session summary for previous session (background Haiku — richer version)
|