claude-mem-lite 2.94.0 → 2.95.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/hook-update.mjs +102 -7
- package/package.json +1 -1
- package/scripts/hook-launcher.mjs +25 -0
- package/scripts/launch.mjs +21 -0
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "claude-mem-lite",
|
|
13
|
-
"version": "2.
|
|
13
|
+
"version": "2.95.0",
|
|
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": "2.
|
|
3
|
+
"version": "2.95.0",
|
|
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/hook-update.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { execSync, execFileSync } from 'node:child_process';
|
|
6
6
|
import { readFileSync, writeFileSync, copyFileSync, cpSync, readdirSync, existsSync, lstatSync, mkdirSync, rmSync, renameSync, chmodSync } from 'node:fs';
|
|
7
|
-
import { join, dirname } from 'node:path';
|
|
7
|
+
import { join, dirname, resolve } from 'node:path';
|
|
8
8
|
import { pathToFileURL } from 'node:url';
|
|
9
9
|
import { tmpdir, homedir } from 'node:os';
|
|
10
10
|
import { DB_DIR, CODE_DIR } from './schema.mjs';
|
|
@@ -356,7 +356,15 @@ export function validateExtractedTarball(sourceDir, expectedVersion, expectedNam
|
|
|
356
356
|
return { ok: true };
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
-
|
|
359
|
+
// opts.skipNpmInstall — copy + atomically switch the source files WITHOUT
|
|
360
|
+
// running `npm install` in staging. Used by syncDataDirFromCache: when the
|
|
361
|
+
// source is a local plugin-cache version (not a downloaded tarball), the
|
|
362
|
+
// target data dir already carries a working, ABI-correct node_modules, so a
|
|
363
|
+
// reinstall is pure cost. With staging holding no node_modules the switch loop
|
|
364
|
+
// below skips the 'node_modules' switchable path (existsSync guard), leaving
|
|
365
|
+
// the target's node_modules untouched. Dependency bumps still flow through the
|
|
366
|
+
// GitHub-tarball path (downloadAndInstall), which keeps skipNpmInstall=false.
|
|
367
|
+
export async function installExtractedRelease(sourceDir, targetDir = INSTALL_DIR, opts = {}) {
|
|
360
368
|
const ts = `${Date.now()}-${process.pid}`;
|
|
361
369
|
const stagingDir = join(targetDir, `.update-staging-${ts}`);
|
|
362
370
|
const backupDir = join(targetDir, `.update-backup-${ts}`);
|
|
@@ -371,11 +379,13 @@ export async function installExtractedRelease(sourceDir, targetDir = INSTALL_DIR
|
|
|
371
379
|
mkdirSync(backupDir, { recursive: true });
|
|
372
380
|
|
|
373
381
|
copyReleaseIntoStaging(sourceDir, stagingDir, manifest);
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
382
|
+
if (!opts.skipNpmInstall) {
|
|
383
|
+
execSync(NPM_INSTALL_CMD, {
|
|
384
|
+
cwd: stagingDir,
|
|
385
|
+
timeout: 60000,
|
|
386
|
+
stdio: 'pipe',
|
|
387
|
+
});
|
|
388
|
+
}
|
|
379
389
|
|
|
380
390
|
for (const relPath of switchablePaths) {
|
|
381
391
|
const stagedPath = join(stagingDir, relPath);
|
|
@@ -456,6 +466,91 @@ export async function installExtractedRelease(sourceDir, targetDir = INSTALL_DIR
|
|
|
456
466
|
}
|
|
457
467
|
}
|
|
458
468
|
|
|
469
|
+
// ── Plugin-cache → data-dir code sync ──────────────────────
|
|
470
|
+
// Root cause this fixes: a plugin-mode install carries TWO independently
|
|
471
|
+
// versioned code copies sharing one DB. The plugin cache
|
|
472
|
+
// (~/.claude/plugins/cache/<mp>/claude-mem-lite/<ver>/) runs the MCP server and
|
|
473
|
+
// is advanced by Claude Code's marketplace updater; on launch it opens the
|
|
474
|
+
// shared DB and migrates the schema FORWARD. The data-dir copy
|
|
475
|
+
// (~/.claude-mem-lite/) backs the standalone CLI symlink and the settings.json
|
|
476
|
+
// hooks, but is only advanced by the GitHub-tarball auto-update — which plugin
|
|
477
|
+
// mode disables (allowInstall=false) and which stalls easily (24h throttle,
|
|
478
|
+
// rate limits, staging npm install). The data-dir code then lags the schema the
|
|
479
|
+
// cache wrote and the CLI/hooks fail to open the DB
|
|
480
|
+
// ("schema is vN but binary supports up to vN-1").
|
|
481
|
+
//
|
|
482
|
+
// Fix: make the data-dir code TRACK the plugin-cache version. The exact files
|
|
483
|
+
// are already on disk in the cache, so this is a local source-file copy — no
|
|
484
|
+
// network, no npm install — and the synced code is precisely the version that
|
|
485
|
+
// migrated the DB, so schema compatibility is guaranteed by construction.
|
|
486
|
+
// node_modules is left untouched (skipNpmInstall). Only ever upgrades; equal
|
|
487
|
+
// versions no-op, which is the natural per-session throttle.
|
|
488
|
+
//
|
|
489
|
+
// opts.sourceDir — explicit source (launch.mjs passes the running ROOT, the
|
|
490
|
+
// exact version that owns the migrated DB). Omitted → scan
|
|
491
|
+
// the plugin cache for the highest valid version.
|
|
492
|
+
// opts.targetDir — defaults to INSTALL_DIR (the homedir code dir, NOT
|
|
493
|
+
// CLAUDE_MEM_DIR — see schema.mjs CODE_DIR / #8632).
|
|
494
|
+
// opts.cacheBase — override the cache root (tests).
|
|
495
|
+
export async function syncDataDirFromCache(opts = {}) {
|
|
496
|
+
try {
|
|
497
|
+
const targetDir = opts.targetDir || INSTALL_DIR;
|
|
498
|
+
|
|
499
|
+
// Dev install: the data-dir entries are symlinks into the source repo.
|
|
500
|
+
// Overwriting them would clobber the working tree — never sync.
|
|
501
|
+
if (isDevMode()) return { synced: false, reason: 'dev-mode' };
|
|
502
|
+
|
|
503
|
+
let sourceDir = opts.sourceDir || null;
|
|
504
|
+
if (!sourceDir) {
|
|
505
|
+
const cacheBase = opts.cacheBase
|
|
506
|
+
|| join(homedir(), '.claude', 'plugins', 'cache', 'sdsrss', 'claude-mem-lite');
|
|
507
|
+
if (!existsSync(cacheBase)) return { synced: false, reason: 'no-cache' };
|
|
508
|
+
const versions = readdirSync(cacheBase)
|
|
509
|
+
.filter(n => /^\d+\.\d+/.test(n))
|
|
510
|
+
.sort((a, b) => compareVersions(b, a)); // newest first
|
|
511
|
+
for (const v of versions) {
|
|
512
|
+
const dir = join(cacheBase, v);
|
|
513
|
+
if (validateExtractedTarball(dir, null).ok) { sourceDir = dir; break; }
|
|
514
|
+
}
|
|
515
|
+
if (!sourceDir) return { synced: false, reason: 'no-valid-cache-version' };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Non-plugin direct install: ROOT === data dir. Syncing a dir onto itself
|
|
519
|
+
// is a no-op at best and a same-path rename hazard at worst.
|
|
520
|
+
if (resolve(sourceDir) === resolve(targetDir)) {
|
|
521
|
+
return { synced: false, reason: 'source-is-target' };
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const val = validateExtractedTarball(sourceDir, null);
|
|
525
|
+
if (!val.ok) return { synced: false, reason: `invalid-source: ${val.reason}` };
|
|
526
|
+
|
|
527
|
+
let sourceVersion;
|
|
528
|
+
try {
|
|
529
|
+
sourceVersion = JSON.parse(readFileSync(join(sourceDir, 'package.json'), 'utf8')).version;
|
|
530
|
+
} catch { return { synced: false, reason: 'source-version-unreadable' }; }
|
|
531
|
+
|
|
532
|
+
let dataVersion = '0.0.0';
|
|
533
|
+
try {
|
|
534
|
+
dataVersion = JSON.parse(readFileSync(join(targetDir, 'package.json'), 'utf8')).version || '0.0.0';
|
|
535
|
+
} catch { /* missing/corrupt target package.json → treat as 0.0.0, sync */ }
|
|
536
|
+
|
|
537
|
+
// Only ever upgrade. Equal → no-op (cheap version compare runs every session).
|
|
538
|
+
if (compareVersions(sourceVersion, dataVersion) <= 0) {
|
|
539
|
+
return { synced: false, reason: 'data-dir-current', sourceVersion, dataVersion };
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
debugLog('DEBUG', 'hook-update',
|
|
543
|
+
`Syncing data-dir code ${dataVersion} → ${sourceVersion} from plugin cache (${sourceDir})`);
|
|
544
|
+
const ok = await installExtractedRelease(sourceDir, targetDir, { skipNpmInstall: true });
|
|
545
|
+
return ok
|
|
546
|
+
? { synced: true, from: dataVersion, to: sourceVersion }
|
|
547
|
+
: { synced: false, reason: 'install-failed', from: dataVersion, to: sourceVersion };
|
|
548
|
+
} catch (err) {
|
|
549
|
+
debugCatch(err, 'syncDataDirFromCache');
|
|
550
|
+
return { synced: false, reason: 'error' };
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
459
554
|
function copyReleaseIntoStaging(sourceDir, stagingDir, manifest = { SOURCE_FILES: LOCAL_SOURCE_FILES, HOOK_SCRIPT_FILES: LOCAL_HOOK_SCRIPT_FILES }) {
|
|
460
555
|
let copied = 0;
|
|
461
556
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.95.0",
|
|
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
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@10.9.2",
|
|
@@ -104,6 +104,31 @@ async function attemptHeal(reason) {
|
|
|
104
104
|
return result.status === 0;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// Defense-in-depth for plugin-mode version drift: the plugin-cache MCP server
|
|
108
|
+
// (kept current by Claude Code) migrates the shared DB schema forward, while
|
|
109
|
+
// this data-dir code (the standalone CLI + these hooks) is only advanced by the
|
|
110
|
+
// GitHub-tarball auto-update, which plugin mode disables — so it can lag the
|
|
111
|
+
// schema and fail to open the DB. syncDataDirFromCache copies the current cache
|
|
112
|
+
// source files locally to close that gap. launch.mjs (MCP start) is the primary
|
|
113
|
+
// healer; this is a backup that also covers the case where the MCP server never
|
|
114
|
+
// starts. Gated to session-start (once per session, OFF the per-tool hot path)
|
|
115
|
+
// and fully best-effort: a stale module without the fn, or any error, just
|
|
116
|
+
// falls through to the normal entry import. The dynamic import keeps this
|
|
117
|
+
// launcher's pure-`node:` static-import charter intact (it must survive a broken
|
|
118
|
+
// install even if hook-update.mjs is unimportable).
|
|
119
|
+
async function trySyncDataDirFromCache() {
|
|
120
|
+
try {
|
|
121
|
+
const { syncDataDirFromCache } = await import(
|
|
122
|
+
pathToFileURL(join(INSTALL_DIR, 'hook-update.mjs')).href
|
|
123
|
+
);
|
|
124
|
+
if (typeof syncDataDirFromCache === 'function') await syncDataDirFromCache();
|
|
125
|
+
} catch { /* best-effort — proceed to the normal entry regardless */ }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (rest.includes('session-start')) {
|
|
129
|
+
await trySyncDataDirFromCache();
|
|
130
|
+
}
|
|
131
|
+
|
|
107
132
|
try {
|
|
108
133
|
await runEntry();
|
|
109
134
|
} catch (e) {
|
package/scripts/launch.mjs
CHANGED
|
@@ -74,6 +74,27 @@ try {
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// Keep the data-dir code (~/.claude-mem-lite/ — backs the standalone CLI symlink
|
|
78
|
+
// and the settings.json hooks) in lockstep with THIS running version. In plugin
|
|
79
|
+
// mode the MCP server runs from the plugin cache (kept current by Claude Code's
|
|
80
|
+
// marketplace updater) and migrates the shared DB schema forward; the data-dir
|
|
81
|
+
// copy is only advanced by the GitHub-tarball auto-update, which plugin mode
|
|
82
|
+
// disables and which stalls easily, so it drifts behind and the CLI/hooks then
|
|
83
|
+
// fail to open the DB the cache migrated ("schema is vN but binary supports up
|
|
84
|
+
// to vN-1"). syncDataDirFromCache copies the source files locally (no network,
|
|
85
|
+
// no npm install) so the data-dir becomes exactly the version that owns the DB.
|
|
86
|
+
// Best-effort — a sync failure must never block the MCP server launch. It runs
|
|
87
|
+
// from the current cache code, so an already-drifted install self-heals on the
|
|
88
|
+
// next launch once its cache reaches a version carrying this call.
|
|
89
|
+
if (process.env.CLAUDE_PLUGIN_ROOT) {
|
|
90
|
+
try {
|
|
91
|
+
const { syncDataDirFromCache } = await import('../hook-update.mjs');
|
|
92
|
+
await syncDataDirFromCache({ sourceDir: ROOT });
|
|
93
|
+
} catch (e) {
|
|
94
|
+
process.stderr.write(`[claude-mem-lite] data-dir sync skipped: ${e.message}\n`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
77
98
|
// Dev mode: prefer ~/.claude-mem-lite/server.mjs (symlinked to source) over
|
|
78
99
|
// CLAUDE_PLUGIN_ROOT (potentially stale plugin cache). This ensures the MCP
|
|
79
100
|
// server always runs the latest code when installed with `install --dev`.
|