claude-mem-lite 2.31.0 → 2.31.2

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.31.0",
13
+ "version": "2.31.2",
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.31.0",
3
+ "version": "2.31.2",
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-update.mjs CHANGED
@@ -196,6 +196,7 @@ const SOURCE_FILES = [
196
196
  'cli.mjs', 'server.mjs', 'server-internals.mjs', 'tool-schemas.mjs',
197
197
  'hook.mjs', 'hook-shared.mjs', 'hook-llm.mjs', 'hook-memory.mjs', 'skip-tools.mjs',
198
198
  'hook-semaphore.mjs', 'hook-episode.mjs', 'hook-context.mjs', 'hook-handoff.mjs', 'hook-update.mjs',
199
+ 'hook-optimize.mjs', 'plugin-cache-guard.mjs',
199
200
  'haiku-client.mjs', 'utils.mjs', 'schema.mjs', 'package.json', 'package-lock.json', 'skill.md',
200
201
  'registry.mjs', 'registry-scanner.mjs', 'registry-indexer.mjs',
201
202
  'registry-retriever.mjs', 'resource-discovery.mjs',
@@ -289,6 +290,13 @@ export function installExtractedRelease(sourceDir, targetDir = INSTALL_DIR) {
289
290
  // Post-update: prune old plugin cache versions (keep latest 3)
290
291
  try { prunePluginCache(); } catch (e) { debugCatch(e, 'prunePluginCache'); }
291
292
 
293
+ // Post-update: clear cache hooks.json in every remaining version. Claude Code
294
+ // runtime reads plugin hooks from cache, not marketplace source — leaving populated
295
+ // cache hooks.json alongside install.mjs-written settings.json causes double firing.
296
+ // Inline impl (no import of plugin-cache-guard.mjs — this module must run even when
297
+ // the guard module is absent on disk, e.g. auto-upgrading from pre-2.31.2).
298
+ try { clearCacheHookResidue(); } catch (e) { debugCatch(e, 'clearCacheHookResidue'); }
299
+
292
300
  debugLog('DEBUG', 'hook-update', `Auto-update: switched ${installed.length} paths`);
293
301
  return true;
294
302
  } catch (err) {
@@ -348,6 +356,33 @@ function copyReleaseIntoStaging(sourceDir, stagingDir) {
348
356
  debugLog('DEBUG', 'hook-update', `Auto-update staged ${copied} source files`);
349
357
  }
350
358
 
359
+ // ── Cache hook residue clearing ────────────────────────────
360
+ // Inline (does not import plugin-cache-guard.mjs) so hook-update.mjs keeps working
361
+ // even if plugin-cache-guard.mjs is missing on disk in degraded installs.
362
+ export function clearCacheHookResidue() {
363
+ const cacheBase = join(homedir(), '.claude', 'plugins', 'cache', 'sdsrss', 'claude-mem-lite');
364
+ if (!existsSync(cacheBase)) return 0;
365
+ let cleared = 0;
366
+ for (const ver of readdirSync(cacheBase)) {
367
+ const p = join(cacheBase, ver, 'hooks', 'hooks.json');
368
+ if (!existsSync(p)) continue;
369
+ try {
370
+ const h = JSON.parse(readFileSync(p, 'utf8'));
371
+ if (!h.hooks || Object.keys(h.hooks).length === 0) continue;
372
+ writeFileSync(p, JSON.stringify({
373
+ description: h.description || 'claude-mem-lite hooks',
374
+ _note: `Auto-cleared by hook-update.mjs post-install — prevents double hook registration (cache ver: ${ver})`,
375
+ hooks: {},
376
+ }, null, 2) + '\n');
377
+ cleared++;
378
+ } catch { /* ignore single bad entry */ }
379
+ }
380
+ if (cleared > 0) {
381
+ debugLog('DEBUG', 'hook-update', `Cache hooks residue cleared in ${cleared} version(s)`);
382
+ }
383
+ return cleared;
384
+ }
385
+
351
386
  // ── Plugin Cache Pruning ──────────────────────────────────
352
387
  const PLUGIN_CACHE_KEEP = 3;
353
388
 
package/hook.mjs CHANGED
@@ -32,6 +32,16 @@ import { searchRelevantMemories } from './hook-memory.mjs';
32
32
  import { buildAndSaveHandoff, detectContinuationIntent, renderHandoffInjection, extractUnfinishedSummary } from './hook-handoff.mjs';
33
33
  import { checkForUpdate } from './hook-update.mjs';
34
34
  import { handleLLMOptimize } from './hook-optimize.mjs';
35
+ // plugin-cache-guard.mjs loaded dynamically — pre-2.31.2 installs that auto-upgraded
36
+ // from an older hook-update.mjs SOURCE_FILES (which did not list this module) would
37
+ // crash on static import. Degrade gracefully to no-op when the module is absent.
38
+ let _cacheGuardCache = null;
39
+ async function loadCacheGuard() {
40
+ if (_cacheGuardCache !== null) return _cacheGuardCache;
41
+ try { _cacheGuardCache = await import('./plugin-cache-guard.mjs'); }
42
+ catch { _cacheGuardCache = {}; }
43
+ return _cacheGuardCache;
44
+ }
35
45
  import { SKIP_TOOLS, SKIP_PREFIXES } from './skip-tools.mjs';
36
46
  import { getVocabulary } from './tfidf.mjs';
37
47
 
@@ -397,6 +407,24 @@ async function handleStop() {
397
407
  // ─── SessionStart Handler + CLAUDE.md Persistence (Tier 1 A, E) ─────────────
398
408
 
399
409
  async function handleSessionStart() {
410
+ // Plugin cache self-heal: Claude Code auto-updates the marketplace plugin can
411
+ // re-populate cache/<ver>/hooks/hooks.json, reintroducing duplicate hook
412
+ // registration alongside install.mjs-managed settings.json entries. Silently
413
+ // clear — gated by hasInstallManagedHooks to avoid breaking plugin-only users.
414
+ // Dynamic-import fallback: if plugin-cache-guard.mjs is missing (pre-2.31.2
415
+ // auto-upgrade install), skip self-heal instead of crashing the entire hook.
416
+ try {
417
+ const guard = await loadCacheGuard();
418
+ if (guard.hasInstallManagedHooks && guard.hasInstallManagedHooks()) {
419
+ const cleared = guard.clearPluginCacheHooks({
420
+ reason: 'Auto-healed by hook.mjs session-start — install.mjs-managed hooks active in settings.json',
421
+ });
422
+ if (cleared.length > 0) {
423
+ debugLog('DEBUG', 'session-start', `auto-healed stale plugin cache hooks.json in version(s): ${cleared.join(', ')}`);
424
+ }
425
+ }
426
+ } catch (e) { debugCatch(e, 'session-start-cache-heal'); }
427
+
400
428
  // Read CC real session_id from hook stdin — used to scope handoff rows so parallel
401
429
  // sessions for the same project don't clobber each other (see docs/bug.txt).
402
430
  let ccSessionId = null;
package/install.mjs CHANGED
@@ -26,6 +26,7 @@ const PLUGIN_KEY = `claude-mem-lite@${MARKETPLACE_KEY}`;
26
26
  const NPM_INSTALL_CMD = 'npm install --omit=dev --no-audit --no-fund';
27
27
 
28
28
  import { RESOURCE_METADATA } from './install-metadata.mjs';
29
+ import { scanPluginCacheHookPollution } from './plugin-cache-guard.mjs';
29
30
 
30
31
  /**
31
32
  * Derive invocation_name from resource name when metadata doesn't provide one.
@@ -205,6 +206,7 @@ async function install() {
205
206
  'cli.mjs', 'server.mjs', 'server-internals.mjs', 'tool-schemas.mjs',
206
207
  'hook.mjs', 'hook-shared.mjs', 'hook-llm.mjs', 'hook-memory.mjs', 'skip-tools.mjs',
207
208
  'hook-semaphore.mjs', 'hook-episode.mjs', 'hook-context.mjs', 'hook-handoff.mjs', 'hook-update.mjs', 'hook-optimize.mjs',
209
+ 'plugin-cache-guard.mjs',
208
210
  'haiku-client.mjs', 'utils.mjs', 'schema.mjs', 'package.json', 'package-lock.json', 'skill.md',
209
211
  'registry.mjs', 'registry-scanner.mjs', 'registry-indexer.mjs',
210
212
  'registry-retriever.mjs', 'resource-discovery.mjs',
@@ -401,18 +403,43 @@ async function install() {
401
403
  }
402
404
  } catch (e) { warn(`Marketplace hooks dedup: ${e.message}`); }
403
405
 
404
- // Sync launch.mjs to plugin cache — ensures MCP server loads dev code via symlink detection
406
+ // Sync launch.mjs to plugin cache — ensures MCP server loads dev code via symlink detection.
407
+ // ALSO clear cached hooks.json in every version dir — Claude Code runtime reads hooks from
408
+ // ~/.claude/plugins/cache/<mp>/<plugin>/<ver>/hooks/hooks.json, NOT from the marketplace source.
409
+ // Clearing only the marketplace source (above) leaves stale cache copies that double-register
410
+ // hooks alongside install.mjs-written settings.json entries.
405
411
  try {
406
412
  const cacheBase = join(homedir(), '.claude', 'plugins', 'cache', MARKETPLACE_KEY, 'claude-mem-lite');
407
413
  if (existsSync(cacheBase)) {
408
414
  const srcLaunch = join(PROJECT_DIR, 'scripts', 'launch.mjs');
415
+ let clearedHooks = 0;
409
416
  for (const ver of readdirSync(cacheBase)) {
410
- const dest = join(cacheBase, ver, 'scripts', 'launch.mjs');
411
- if (existsSync(join(cacheBase, ver, 'scripts'))) {
412
- copyFileSync(srcLaunch, dest);
417
+ const verDir = join(cacheBase, ver);
418
+
419
+ // Sync launch.mjs
420
+ if (existsSync(join(verDir, 'scripts'))) {
421
+ try { copyFileSync(srcLaunch, join(verDir, 'scripts', 'launch.mjs')); } catch {}
422
+ }
423
+
424
+ // Clear cached hooks.json (runtime reads here, not marketplace source)
425
+ const cachedHooksPath = join(verDir, 'hooks', 'hooks.json');
426
+ if (existsSync(cachedHooksPath)) {
427
+ try {
428
+ const h = JSON.parse(readFileSync(cachedHooksPath, 'utf8'));
429
+ if (h.hooks && Object.keys(h.hooks).length > 0) {
430
+ writeFileSync(cachedHooksPath, JSON.stringify({
431
+ description: h.description || 'claude-mem-lite hooks',
432
+ _note: `Hooks managed by install.mjs in settings.json — cache hooks.json cleared to prevent duplicate registration (cache ver: ${ver})`,
433
+ hooks: {}
434
+ }, null, 2) + '\n');
435
+ clearedHooks++;
436
+ }
437
+ } catch { /* silent — never block install on one bad cache entry */ }
413
438
  }
414
439
  }
415
- ok('Plugin cache: launch.mjs synced (dev mode MCP routing)');
440
+ const parts = ['launch.mjs synced (dev mode MCP routing)'];
441
+ if (clearedHooks > 0) parts.push(`${clearedHooks} stale hooks.json cleared`);
442
+ ok(`Plugin cache: ${parts.join('; ')}`);
416
443
  }
417
444
  } catch (e) { warn(`Plugin cache sync: ${e.message}`); }
418
445
  }
@@ -962,6 +989,18 @@ async function status() {
962
989
  fail('Hooks: not configured');
963
990
  }
964
991
 
992
+ // Plugin cache pollution: populated hooks.json in cache AND install.mjs-managed
993
+ // settings.json hooks → runtime registers both → duplicate firing.
994
+ const polluted = scanPluginCacheHookPollution();
995
+ if (polluted.length > 0 && hasHooks) {
996
+ fail(`Plugin cache: stale hooks.json in version(s) ${polluted.join(', ')} — duplicate firing alongside settings.json (run 'install' to auto-clear)`);
997
+ } else if (polluted.length > 0) {
998
+ // plugin-only mode (no settings.json hooks) — cache hooks.json is the sole source, expected
999
+ ok(`Plugin cache: ${polluted.length} version(s) with hooks.json (plugin-only mode)`);
1000
+ } else if (pluginEnabled || hasHooks) {
1001
+ ok('Plugin cache: no stale hooks.json (no duplicate firing)');
1002
+ }
1003
+
965
1004
  // Database
966
1005
  if (existsSync(DB_PATH)) {
967
1006
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.31.0",
3
+ "version": "2.31.2",
4
4
  "description": "Lightweight persistent memory system for Claude Code",
5
5
  "type": "module",
6
6
  "engines": {