claude-mem-lite 3.57.0 → 3.58.1

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": "3.57.0",
13
+ "version": "3.58.1",
14
14
  "source": "./",
15
15
  "description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark)."
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "3.57.0",
3
+ "version": "3.58.1",
4
4
  "description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark).",
5
5
  "author": {
6
6
  "name": "sdsrss"
package/adopt-cli.mjs CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  } from './memdir.mjs';
23
23
  import {
24
24
  writeManaged, removeManaged, isAdopted as claudeMdIsAdopted,
25
+ hasResidue as claudeMdHasResidue,
25
26
  needsRefresh, migrateLegacyMemoryDir, hasLegacyMemdirSentinel,
26
27
  claudeMdPath, detailDocPath,
27
28
  } from './claudemd.mjs';
@@ -311,8 +312,8 @@ function statusAll() {
311
312
 
312
313
  const known = listKnownProjectDirs();
313
314
  let adoptedCount = 0;
314
- for (const dir of known) if (claudeMdIsAdopted(dir, PLUGIN_SLUG)) adoptedCount++;
315
- log(`[adopt --status] known projects (~/.claude.json): ${known.length} scanned, ${adoptedCount} with a CLAUDE.md managed block.`);
315
+ for (const dir of known) if (claudeMdHasResidue(dir, PLUGIN_SLUG)) adoptedCount++;
316
+ log(`[adopt --status] known projects (~/.claude.json): ${known.length} scanned, ${adoptedCount} with a CLAUDE.md managed block or partial residue (detail doc/state).`);
316
317
  if (adoptedCount > 0) log('[adopt --status] run `claude-mem-lite unadopt --all` to remove every CLAUDE.md block.');
317
318
 
318
319
  const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT ? 'set' : 'unset';
@@ -347,16 +348,20 @@ function unadoptAll(args) {
347
348
 
348
349
  // 1. New scheme: scrub CLAUDE.md managed blocks across known project paths.
349
350
  const projectDirs = listKnownProjectDirs();
350
- let blocks = 0;
351
+ let blocks = 0, partial = 0;
351
352
  for (const dir of projectDirs) {
352
- if (!claudeMdIsAdopted(dir, PLUGIN_SLUG)) continue;
353
+ // hasResidue, not isAdopted: the sweep must also catch PARTIAL residue
354
+ // (block without detail doc, or an orphaned doc/state sidecar) —
355
+ // isAdopted's block-AND-doc gate skipped those projects forever.
356
+ if (!claudeMdHasResidue(dir, PLUGIN_SLUG)) continue;
353
357
  if (dryRun) {
354
- log(`[unadopt --all --dry-run] ${dir} → would-remove CLAUDE.md block + detail doc`);
358
+ log(`[unadopt --all --dry-run] ${dir} → would-remove plugin residue (CLAUDE.md block and/or detail doc/state)`);
355
359
  blocks++;
356
360
  continue;
357
361
  }
358
362
  const r = removeManaged(dir, PLUGIN_SLUG);
359
363
  if (r.action === 'removed') { log(`[unadopt --all] ${dir} → removed`); blocks++; }
364
+ else { log(`[unadopt --all] ${dir} → cleaned partial residue (detail doc/state, no block)`); partial++; }
360
365
  }
361
366
 
362
367
  // 2. Legacy memory-dir cleanup across every memdir (foreign-content guarded).
@@ -372,7 +377,8 @@ function unadoptAll(args) {
372
377
  }
373
378
 
374
379
  log('');
375
- log(`[unadopt --all] ${dryRun ? 'would remove' : 'removed'} ${blocks} CLAUDE.md block(s) across ${projectDirs.length} known project(s); ${legacy} legacy memory-dir sentinel(s) ${dryRun ? 'pending' : 'cleaned'}.`);
380
+ const partialNote = partial > 0 ? ` (+${partial} partial-residue cleanup(s))` : '';
381
+ log(`[unadopt --all] ${dryRun ? 'would remove' : 'removed'} ${blocks} CLAUDE.md block(s)${partialNote} across ${projectDirs.length} known project(s); ${legacy} legacy memory-dir sentinel(s) ${dryRun ? 'pending' : 'cleaned'}.`);
376
382
  if (projectDirs.length === 0) {
377
383
  log('[unadopt --all] no known projects found in ~/.claude.json — if a project was adopted but never opened in Claude Code, run `claude-mem-lite unadopt` from inside it.');
378
384
  }
@@ -389,7 +395,7 @@ export function cmdUnadopt(args = []) {
389
395
 
390
396
  const cwd = detectCwd();
391
397
  if (dryRun) {
392
- const blockState = claudeMdIsAdopted(cwd, PLUGIN_SLUG) ? 'would-remove CLAUDE.md block + detail doc' : 'no CLAUDE.md block';
398
+ const blockState = claudeMdHasResidue(cwd, PLUGIN_SLUG) ? 'would-remove CLAUDE.md block + detail doc' : 'no CLAUDE.md block';
393
399
  const legacy = hasLegacyMemdirSentinel(cwd, PLUGIN_SLUG) ? 'would-clean legacy memory-dir sentinel' : 'no legacy residue';
394
400
  log(`[unadopt --dry-run] ${cwd}`);
395
401
  log(` ${blockState}`);
package/claudemd.mjs CHANGED
@@ -98,6 +98,20 @@ export function isAdopted(cwd, slug) {
98
98
  return blk.body !== null && existsSync(detailDocPath(cwd, slug));
99
99
  }
100
100
 
101
+ /**
102
+ * Any trace of adoption that unadopt should clean: managed block OR detail doc
103
+ * OR state sidecar. Deliberately weaker than isAdopted (whose AND lets a
104
+ * half-written adopt self-heal on the next SessionStart): the unadopt sweep
105
+ * gated on isAdopted skipped partial residue forever — e.g. a user deleted the
106
+ * detail doc but the CLAUDE.md block remained, and `unadopt --all` never
107
+ * removed it. removeManaged cleans all three pieces, so sweep on any of them.
108
+ */
109
+ export function hasResidue(cwd, slug) {
110
+ return readBlock(cwd, slug).body !== null
111
+ || existsSync(detailDocPath(cwd, slug))
112
+ || existsSync(stateFilePath(cwd, slug));
113
+ }
114
+
101
115
  /**
102
116
  * Whether the installed block/doc has drifted from the shipped content — i.e.
103
117
  * a version bump or a template edit means we should refresh. Returns true when
package/hook-shared.mjs CHANGED
@@ -6,7 +6,7 @@ import { randomUUID } from 'crypto';
6
6
  import { join } from 'path';
7
7
  import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync, readdirSync, statSync, unlinkSync, chmodSync } from 'fs';
8
8
  import { inferProject, debugCatch } from './utils.mjs';
9
- import { ensureDb, DB_DIR } from './schema.mjs';
9
+ import { ensureDbWithWalRecovery, DB_DIR } from './schema.mjs';
10
10
  import { getClaudePath as getClaudePathShared, resolveModel as resolveModelShared, flattenForCLI as _flattenForCLI, detectMode as detectLLMMode, callHaiku } from './haiku-client.mjs';
11
11
  // Phase D: invited-memory sentinel detection. memdir.mjs/claudemd.mjs only pull in
12
12
  // fs/path/os/crypto; adopt-content.mjs is pure strings. No circular deps —
@@ -154,7 +154,9 @@ export function createSessionId() {
154
154
 
155
155
  export function openDb() {
156
156
  try {
157
- return ensureDb();
157
+ // WAL-corruption self-heal (was server.mjs-only): without it, hooks stayed
158
+ // silently dead (null DB) on a corrupt WAL until the next MCP server start.
159
+ return ensureDbWithWalRecovery();
158
160
  } catch {
159
161
  return null;
160
162
  }
package/hook-update.mjs CHANGED
@@ -635,6 +635,32 @@ function smokeInstalledRelease(targetDir) {
635
635
  const p = join(targetDir, entry);
636
636
  if (existsSync(p)) execSync(`${q(process.execPath)} --check ${q(p)}`, { timeout: 10000, stdio: 'ignore' });
637
637
  }
638
+ // `cli.mjs help` exits without opening the DB, so it cannot see a
639
+ // present-but-unusable better-sqlite3 binding: npm >= 12 blocks
640
+ // install/lifecycle scripts by default, so the staging `npm install`
641
+ // above exits 0 with the native .node never compiled (a Node major bump
642
+ // strands a stale ABI the same way). Direct installs register server.mjs
643
+ // without the launch.mjs probe, so this gate is their only check. Probe
644
+ // in a child process (execSync so the unit-test mock intercepts, and so
645
+ // the running old-version process's require cache can't mask it); on
646
+ // failure rebuild with scripts enabled for just this dep — plain-rebuild
647
+ // fallback for older npm — then re-probe. A still-broken binding throws
648
+ // out of the try, smoke fails, and the caller rolls back to the old
649
+ // (working) install.
650
+ if (existsSync(join(targetDir, 'node_modules', 'better-sqlite3'))) {
651
+ const probeSrc = 'const{createRequire}=require("node:module");const D=createRequire(process.argv[1])("better-sqlite3");new D(":memory:").close();';
652
+ const probeCmd = `${q(process.execPath)} -e ${q(probeSrc)} ${q(join(targetDir, 'package.json'))}`;
653
+ try {
654
+ execSync(probeCmd, { timeout: 20000, stdio: 'ignore' });
655
+ } catch {
656
+ try {
657
+ execSync('npm rebuild better-sqlite3 --dangerously-allow-all-scripts', { cwd: targetDir, timeout: 120000, stdio: 'ignore' });
658
+ } catch {
659
+ execSync('npm rebuild better-sqlite3', { cwd: targetDir, timeout: 120000, stdio: 'ignore' });
660
+ }
661
+ execSync(probeCmd, { timeout: 20000, stdio: 'ignore' });
662
+ }
663
+ }
638
664
  return true;
639
665
  } catch (e) {
640
666
  debugLog('WARN', 'hook-update', `post-install smoke failed (rolling back): ${e.message}`);
package/install.mjs CHANGED
@@ -466,7 +466,7 @@ if (IS_DEV) {
466
466
  ok(`better-sqlite3: ${verify.action}`);
467
467
  } else {
468
468
  fail(`better-sqlite3 binding unusable after rebuild: ${verify.error}`);
469
- log('Try manually: cd ' + INSTALL_DIR + ' && npm rebuild better-sqlite3 --build-from-source');
469
+ log('Try manually: cd ' + INSTALL_DIR + ' && npm rebuild better-sqlite3 --dangerously-allow-all-scripts');
470
470
  process.exit(1);
471
471
  }
472
472
  }
package/mem-cli.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  // No MCP SDK or heavy deps — only imports schema.mjs and utils.mjs
4
4
 
5
5
  import { homedir } from 'os';
6
- import { ensureDb, DB_PATH, DB_DIR, REGISTRY_DB_PATH } from './schema.mjs';
6
+ import { ensureDbWithWalRecovery, DB_PATH, DB_DIR, REGISTRY_DB_PATH } from './schema.mjs';
7
7
  import { truncate, typeIcon, inferProject, scrubSecrets } from './utils.mjs';
8
8
  import { resolveProject } from './project-utils.mjs';
9
9
  import { TIER_CASE_SQL, tierSqlParams } from './tier.mjs';
@@ -3156,14 +3156,16 @@ export async function run(argv) {
3156
3156
  }
3157
3157
 
3158
3158
  // adopt / unadopt do pure filesystem work on ~/.claude/projects/<encoded>/memory/ —
3159
- // no DB needed. Route them before ensureDb() so an unbootable DB doesn't block.
3159
+ // no DB needed. Route them before the DB open so an unbootable DB doesn't block.
3160
3160
  if (cmd === 'adopt') { cmdAdopt(cmdArgs); return; }
3161
3161
  if (cmd === 'unadopt') { cmdUnadopt(cmdArgs); return; }
3162
3162
  if (cmd === 'memdir-audit') { cmdMemdirAudit(cmdArgs); return; }
3163
3163
 
3164
3164
  let db;
3165
3165
  try {
3166
- db = ensureDb();
3166
+ // Corruption-gated WAL recovery (shared with server/hooks) — a corrupt WAL
3167
+ // previously threw here with no auto-repair until the next MCP start.
3168
+ db = ensureDbWithWalRecovery({ warn: (m) => process.stderr.write(`[mem] ${m}\n`) });
3167
3169
  } catch (e) {
3168
3170
  out(`[mem] Error: Cannot open database: ${e.message}`);
3169
3171
  out(`[mem] DB path: ${DB_PATH}`);