claude-mem-lite 4.0.0 → 4.0.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/hook-optimize.mjs +3 -3
- package/install.mjs +77 -31
- package/lib/binding-probe.mjs +48 -4
- package/lib/install-shape.mjs +2 -2
- package/npm-shrinkwrap.json +235 -377
- package/package.json +3 -3
- package/scripts/binding-probe-cli.mjs +8 -0
- package/scripts/hook-launcher.mjs +9 -3
- package/scripts/launch.mjs +3 -4
- package/scripts/setup.sh +4 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "claude-mem-lite",
|
|
13
|
-
"version": "4.0.
|
|
13
|
+
"version": "4.0.2",
|
|
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": "4.0.
|
|
3
|
+
"version": "4.0.2",
|
|
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/README.md
CHANGED
|
@@ -145,7 +145,7 @@ How claude-mem-lite differs from the major neighbors in the LLM-memory space (ve
|
|
|
145
145
|
|
|
146
146
|
## Requirements
|
|
147
147
|
|
|
148
|
-
- **Node.js** >=
|
|
148
|
+
- **Node.js** >= 22
|
|
149
149
|
- **Claude Code** CLI installed and configured (`claude` command available)
|
|
150
150
|
- **SQLite3** support (provided by `better-sqlite3`, compiled on install)
|
|
151
151
|
- **Platform**: Linux or macOS (see [Platform Support](#platform-support))
|
package/hook-optimize.mjs
CHANGED
|
@@ -169,7 +169,7 @@ export function findReenrichCandidates(db, limit = 10, { scope = 'narrow', proje
|
|
|
169
169
|
const stmt = db.prepare(`
|
|
170
170
|
SELECT id, title, narrative, type, subtitle, importance, project
|
|
171
171
|
FROM observations
|
|
172
|
-
WHERE
|
|
172
|
+
WHERE ${liveObsFilterSql('')}
|
|
173
173
|
AND (concepts IS NULL OR concepts = '')
|
|
174
174
|
AND (facts IS NULL OR facts = '')
|
|
175
175
|
AND lesson_learned IS NULL
|
|
@@ -468,7 +468,7 @@ export function extractUniqueConcepts(db, limit = 500, { project } = {}) {
|
|
|
468
468
|
const projectClause = project ? 'AND project = ?' : '';
|
|
469
469
|
const stmt = db.prepare(`
|
|
470
470
|
SELECT concepts FROM observations
|
|
471
|
-
WHERE
|
|
471
|
+
WHERE ${liveObsFilterSql('')}
|
|
472
472
|
AND concepts IS NOT NULL AND concepts != ''
|
|
473
473
|
${projectClause}
|
|
474
474
|
ORDER BY created_at_epoch DESC
|
|
@@ -536,7 +536,7 @@ export function applyNormalization(db, groups, { project = null } = {}) {
|
|
|
536
536
|
.prepare(
|
|
537
537
|
`
|
|
538
538
|
SELECT id, title, narrative, concepts, search_aliases, lesson_learned FROM observations
|
|
539
|
-
WHERE
|
|
539
|
+
WHERE ${liveObsFilterSql('')}
|
|
540
540
|
AND concepts IS NOT NULL AND concepts != ''
|
|
541
541
|
AND (? IS NULL OR project = ?)
|
|
542
542
|
`,
|
package/install.mjs
CHANGED
|
@@ -68,7 +68,7 @@ import { SOURCE_FILES, HOOK_SCRIPT_FILES } from './source-files.mjs';
|
|
|
68
68
|
import {
|
|
69
69
|
probeBetterSqlite3Binding,
|
|
70
70
|
ensureBetterSqlite3Working,
|
|
71
|
-
|
|
71
|
+
nativeBindingRepairHint,
|
|
72
72
|
} from './lib/binding-probe.mjs';
|
|
73
73
|
import { detectInstallShape, probeRuntimeRoots } from './lib/install-shape.mjs';
|
|
74
74
|
import { clearNativeBindingBreakage, readNativeBindingBreakage } from './lib/native-binding-hint.mjs';
|
|
@@ -404,6 +404,57 @@ export function patchClaudeMdVersion(text, version) {
|
|
|
404
404
|
// at the same red line. Claude Code also seeds a NEW cache version from that same
|
|
405
405
|
// emptied clone. So check the source before prescribing it, and fall back to a
|
|
406
406
|
// reinstall — which re-clones the manifest from the repo — when it is empty too.
|
|
407
|
+
/**
|
|
408
|
+
* Clear whatever occupies `p` — file, directory, or symlink INCLUDING a dangling one —
|
|
409
|
+
* before a symlink is written over it. Returns true when something was removed.
|
|
410
|
+
*
|
|
411
|
+
* A20260906-R8-P2-3: every call site used to gate on `existsSync(p)`, which FOLLOWS the
|
|
412
|
+
* link, so a dangling symlink reads as absent. Two consequences, and the second is the
|
|
413
|
+
* one that bites: `uninstall` leaves a dead `claude-mem-lite` on PATH, and
|
|
414
|
+
* `createCliSymlink` skips the removal, `symlinkSync` throws EEXIST, the catch falls back
|
|
415
|
+
* to an unwritable /usr/local/bin, and the user is told "CLI symlink failed — run
|
|
416
|
+
* manually". Re-running `install` — the documented repair — cannot repair it. The same
|
|
417
|
+
* file already knew the idiom: isDevInstall() pairs existsSync with lstatSync.
|
|
418
|
+
*
|
|
419
|
+
* rmSync rather than unlinkSync because the dev-mode sites link DIRECTORIES; verified it
|
|
420
|
+
* removes the link and leaves the target intact (a link to a populated dir, target still
|
|
421
|
+
* readable afterwards) — following it would delete the developer's own scripts/.
|
|
422
|
+
*
|
|
423
|
+
* @param {string} p
|
|
424
|
+
* @returns {boolean}
|
|
425
|
+
*/
|
|
426
|
+
export function clearLinkPath(p) {
|
|
427
|
+
try {
|
|
428
|
+
lstatSync(p);
|
|
429
|
+
} catch {
|
|
430
|
+
return false; // genuinely absent — nothing to clear
|
|
431
|
+
}
|
|
432
|
+
try {
|
|
433
|
+
rmSync(p, { recursive: true, force: true });
|
|
434
|
+
return true;
|
|
435
|
+
} catch {
|
|
436
|
+
return false; // permissions; callers fall back or report their own failure
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Minimum supported Node MAJOR, parsed out of a `package.json#engines.node` range.
|
|
442
|
+
*
|
|
443
|
+
* A20260906-R8-P2-1: doctor carried its own `>= 18` literal, so v4.0.0 raised the real
|
|
444
|
+
* floor to 22 (npm refuses to install below it, and better-sqlite3 13 ships no prebuild
|
|
445
|
+
* for those Nodes) while doctor kept printing `✓ Node.js: v20.x` — greenlighting the
|
|
446
|
+
* runtime that WAS the fault, in the one tool a broken user is told to run. One source,
|
|
447
|
+
* so the two cannot drift again; the fallback only covers an unreadable manifest.
|
|
448
|
+
*
|
|
449
|
+
* @param {unknown} enginesNode e.g. '>=22' or '^22.12.0 || ^24.0.0 || >=26.0.0'
|
|
450
|
+
* @param {number} [fallback]
|
|
451
|
+
* @returns {number}
|
|
452
|
+
*/
|
|
453
|
+
export function requiredNodeMajor(enginesNode, fallback = 22) {
|
|
454
|
+
const m = /(\d+)/.exec(String(enginesNode ?? ''));
|
|
455
|
+
return m ? Number(m[1]) : fallback;
|
|
456
|
+
}
|
|
457
|
+
|
|
407
458
|
export function hookManifestRepairHint(cacheRoot, marketplaceRoot) {
|
|
408
459
|
const src = join(marketplaceRoot, 'hooks', 'hooks.json');
|
|
409
460
|
const dst = join(cacheRoot, 'hooks', 'hooks.json');
|
|
@@ -513,34 +564,22 @@ function installSourceFiles(IS_DEV) {
|
|
|
513
564
|
// Ensure parent dir exists for subdir entries (e.g. 'lib/activity.mjs')
|
|
514
565
|
const linkParent = dirname(link);
|
|
515
566
|
if (!existsSync(linkParent)) mkdirSync(linkParent, { recursive: true });
|
|
516
|
-
// Remove existing file/symlink before creating
|
|
517
|
-
|
|
518
|
-
try {
|
|
519
|
-
unlinkSync(link);
|
|
520
|
-
} catch {}
|
|
567
|
+
// Remove existing file/symlink (including a dangling one) before creating.
|
|
568
|
+
clearLinkPath(link);
|
|
521
569
|
symlinkSync(target, link);
|
|
522
570
|
}
|
|
523
571
|
}
|
|
524
572
|
// Symlink scripts/ directory
|
|
525
573
|
const scriptsLink = join(DATA_DIR, 'scripts');
|
|
526
|
-
|
|
527
|
-
try {
|
|
528
|
-
rmSync(scriptsLink, { recursive: true, force: true });
|
|
529
|
-
} catch {}
|
|
574
|
+
clearLinkPath(scriptsLink);
|
|
530
575
|
symlinkSync(join(PROJECT_DIR, 'scripts'), scriptsLink);
|
|
531
576
|
// Symlink node_modules/
|
|
532
577
|
const nmLink = join(DATA_DIR, 'node_modules');
|
|
533
|
-
|
|
534
|
-
try {
|
|
535
|
-
rmSync(nmLink, { recursive: true, force: true });
|
|
536
|
-
} catch {}
|
|
578
|
+
clearLinkPath(nmLink);
|
|
537
579
|
symlinkSync(join(PROJECT_DIR, 'node_modules'), nmLink);
|
|
538
580
|
// Symlink registry/ directory
|
|
539
581
|
const regLink = join(DATA_DIR, 'registry');
|
|
540
|
-
|
|
541
|
-
try {
|
|
542
|
-
rmSync(regLink, { recursive: true, force: true });
|
|
543
|
-
} catch {}
|
|
582
|
+
clearLinkPath(regLink);
|
|
544
583
|
if (existsSync(join(PROJECT_DIR, 'registry'))) {
|
|
545
584
|
symlinkSync(join(PROJECT_DIR, 'registry'), regLink);
|
|
546
585
|
}
|
|
@@ -620,9 +659,7 @@ async function installDependencies(IS_DEV) {
|
|
|
620
659
|
ok(`better-sqlite3: ${verify.action}`);
|
|
621
660
|
} else {
|
|
622
661
|
fail(`better-sqlite3 binding unusable after rebuild: ${verify.error}`);
|
|
623
|
-
log(
|
|
624
|
-
'Try manually: cd ' + INSTALL_DIR + ' && npm rebuild better-sqlite3 --dangerously-allow-all-scripts',
|
|
625
|
-
);
|
|
662
|
+
log(`Try manually: ${nativeBindingRepairHint(INSTALL_DIR)}`);
|
|
626
663
|
process.exit(1);
|
|
627
664
|
}
|
|
628
665
|
|
|
@@ -643,7 +680,7 @@ async function installDependencies(IS_DEV) {
|
|
|
643
680
|
`better-sqlite3 unusable in the package this installer runs from (${PROJECT_DIR}): ${selfVerify.error}`,
|
|
644
681
|
);
|
|
645
682
|
log(
|
|
646
|
-
` The install itself is fine; the \`claude-mem-lite\` shell command will self-heal on first use, or run:
|
|
683
|
+
` The install itself is fine; the \`claude-mem-lite\` shell command will self-heal on first use, or run: ${nativeBindingRepairHint(PROJECT_DIR)}`,
|
|
647
684
|
);
|
|
648
685
|
}
|
|
649
686
|
}
|
|
@@ -662,14 +699,14 @@ function createCliSymlink() {
|
|
|
662
699
|
const cliLink = join(localBin, 'claude-mem-lite');
|
|
663
700
|
try {
|
|
664
701
|
if (!existsSync(localBin)) mkdirSync(localBin, { recursive: true });
|
|
665
|
-
|
|
702
|
+
clearLinkPath(cliLink);
|
|
666
703
|
symlinkSync(cliSource, cliLink);
|
|
667
704
|
ok(`CLI: ${cliLink} → ${cliSource}`);
|
|
668
705
|
} catch {
|
|
669
706
|
// Fallback: try /usr/local/bin (may need sudo)
|
|
670
707
|
try {
|
|
671
708
|
const globalLink = '/usr/local/bin/claude-mem-lite';
|
|
672
|
-
|
|
709
|
+
clearLinkPath(globalLink);
|
|
673
710
|
symlinkSync(cliSource, globalLink);
|
|
674
711
|
ok(`CLI: ${globalLink} → ${cliSource}`);
|
|
675
712
|
} catch {
|
|
@@ -1545,8 +1582,7 @@ async function uninstall() {
|
|
|
1545
1582
|
for (const binDir of [join(homedir(), '.local', 'bin'), '/usr/local/bin']) {
|
|
1546
1583
|
const cliLink = join(binDir, 'claude-mem-lite');
|
|
1547
1584
|
try {
|
|
1548
|
-
if (
|
|
1549
|
-
unlinkSync(cliLink);
|
|
1585
|
+
if (clearLinkPath(cliLink)) {
|
|
1550
1586
|
ok(`CLI symlink removed: ${cliLink}`);
|
|
1551
1587
|
}
|
|
1552
1588
|
} catch {
|
|
@@ -1901,12 +1937,22 @@ async function doctor() {
|
|
|
1901
1937
|
warn(msg);
|
|
1902
1938
|
};
|
|
1903
1939
|
|
|
1904
|
-
// Node version
|
|
1940
|
+
// Node version. The floor is read from package.json#engines rather than restated here —
|
|
1941
|
+
// see requiredNodeMajor. It is PRINTED on the ok line too, so the guard test has something
|
|
1942
|
+
// to compare against the manifest; a floor nobody can observe is a floor nobody notices
|
|
1943
|
+
// has gone stale, which is exactly how the `>= 18` literal outlived the v4.0.0 bump.
|
|
1905
1944
|
const nodeVer = process.version;
|
|
1906
|
-
|
|
1907
|
-
|
|
1945
|
+
let enginesNode = null;
|
|
1946
|
+
try {
|
|
1947
|
+
enginesNode = JSON.parse(readFileSync(join(PROJECT_DIR, 'package.json'), 'utf8')).engines?.node;
|
|
1948
|
+
} catch {
|
|
1949
|
+
/* unreadable manifest → requiredNodeMajor's fallback */
|
|
1950
|
+
}
|
|
1951
|
+
const nodeFloor = requiredNodeMajor(enginesNode);
|
|
1952
|
+
if (parseInt(nodeVer.slice(1)) >= nodeFloor) {
|
|
1953
|
+
ok(`Node.js: ${nodeVer} (>=${nodeFloor} required)`);
|
|
1908
1954
|
} else {
|
|
1909
|
-
fail(`Node.js ${nodeVer} too old (need
|
|
1955
|
+
fail(`Node.js ${nodeVer} too old (need >=${nodeFloor})`);
|
|
1910
1956
|
issues++;
|
|
1911
1957
|
}
|
|
1912
1958
|
|
|
@@ -3094,7 +3140,7 @@ async function rebuildBinding() {
|
|
|
3094
3140
|
ok(`better-sqlite3 binding ${verify.action} for Node ${process.version} — ${label} (${root})`);
|
|
3095
3141
|
} else {
|
|
3096
3142
|
fail(`better-sqlite3 binding still unusable in ${label}: ${verify.error}`);
|
|
3097
|
-
log(`Try manually:
|
|
3143
|
+
log(`Try manually: ${nativeBindingRepairHint(root)}`);
|
|
3098
3144
|
failed++;
|
|
3099
3145
|
}
|
|
3100
3146
|
}
|
package/lib/binding-probe.mjs
CHANGED
|
@@ -11,8 +11,10 @@ import { createRequire } from 'node:module';
|
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
|
|
13
13
|
// npm >= 12 blocks lifecycle scripts by default, so a plain `npm rebuild` exits 0
|
|
14
|
-
// WITHOUT compiling — see the rebuild() comment below.
|
|
15
|
-
//
|
|
14
|
+
// WITHOUT compiling — see the rebuild() comment below. Step 1 of the heal chain, and
|
|
15
|
+
// correct for a dependency that declares an install script. It is NOT sufficient for the
|
|
16
|
+
// better-sqlite3 the project actually ships — see the constant below, and never hand this
|
|
17
|
+
// one to a human on its own (nativeBindingRepairHint is what surfaces get).
|
|
16
18
|
export const NATIVE_BINDING_REBUILD_CMD = 'npm rebuild better-sqlite3 --dangerously-allow-all-scripts';
|
|
17
19
|
|
|
18
20
|
// Last-resort heal, added in v4.0.0 with better-sqlite3 13. THE npm-REBUILD PATH ABOVE
|
|
@@ -29,6 +31,33 @@ export const NATIVE_BINDING_REBUILD_CMD = 'npm rebuild better-sqlite3 --dangerou
|
|
|
29
31
|
// nothing → this command produces build/Release/better_sqlite3.node and the DB opens.
|
|
30
32
|
export const NATIVE_BINDING_SOURCE_BUILD_CMD = 'npm run --prefix node_modules/better-sqlite3 build-release';
|
|
31
33
|
|
|
34
|
+
/**
|
|
35
|
+
* The one-liner a HUMAN should be given to repair the binding under `root`.
|
|
36
|
+
*
|
|
37
|
+
* A20260906-R8-P1-1: every user-facing "Repair:" line printed NATIVE_BINDING_REBUILD_CMD
|
|
38
|
+
* alone — including doctor's, via install-shape. On better-sqlite3 13 that command prints
|
|
39
|
+
* "rebuilt dependencies successfully", exits 0, and compiles nothing (re-measured
|
|
40
|
+
* 2026-09-06, npm 12.0.2, both prebuild states), so the copy-paste repair REPORTS SUCCESS
|
|
41
|
+
* on a still-dead binding. That is worse than printing nothing. The command that heals was
|
|
42
|
+
* added in v4.0.0 but lived only inside the automated chain and was shown to nobody.
|
|
43
|
+
*
|
|
44
|
+
* This is the same defect the CHANGELOG records once already: the hints used to say
|
|
45
|
+
* `--build-from-source`, which no-oped the same way, and were changed to the flag above.
|
|
46
|
+
* That fix was right for better-sqlite3 12 and expired when the dependency was bumped.
|
|
47
|
+
*
|
|
48
|
+
* `&&`, never `||`: step 1 exits 0 whether or not it did anything, so an `||` chain would
|
|
49
|
+
* never reach step 2 — the no-op is the whole trap. Sequencing unconditionally costs a
|
|
50
|
+
* recompile in the case step 1 already fixed, which is the right trade for a manual repair.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} root Directory whose node_modules holds better-sqlite3
|
|
53
|
+
* @returns {string}
|
|
54
|
+
*/
|
|
55
|
+
export function nativeBindingRepairHint(root) {
|
|
56
|
+
// Quoted: INSTALL_DIR / a plugin-cache root can contain spaces, and an unquoted `cd`
|
|
57
|
+
// hands the user a command that fails on exactly the machines least able to debug it.
|
|
58
|
+
return `cd "${root}" && ${NATIVE_BINDING_REBUILD_CMD} && ${NATIVE_BINDING_SOURCE_BUILD_CMD}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
32
61
|
// Set on a re-exec'd child so one failed heal cannot fork-bomb the CLI.
|
|
33
62
|
export const BINDING_HEAL_GUARD_ENV = 'CLAUDE_MEM_BINDING_HEALED';
|
|
34
63
|
|
|
@@ -243,9 +272,24 @@ export async function ensureBetterSqlite3Working(installDir, deps = {}) {
|
|
|
243
272
|
// get it appended — otherwise an injected strategy would silently grow a step its owner
|
|
244
273
|
// never asked for, and (as the stub tests caught) a test that stubs `rebuild` without
|
|
245
274
|
// stubbing `exec` would shell out for real.
|
|
275
|
+
//
|
|
276
|
+
// `sourceBuild: false` is the EXPLICIT opt-out, and it exists because the `deps.rebuild`
|
|
277
|
+
// test above was the wrong proxy for "this caller has a budget" (A20260906-R8b-P0-1, found
|
|
278
|
+
// by independent review). The one caller that is genuinely time-boxed — the SessionStart
|
|
279
|
+
// hook probe — injects `exec` with a 20 s cap and does NOT inject `rebuild`, so it landed
|
|
280
|
+
// outside the guard. That matters because this step is `node-gyp clean && node-gyp rebuild`:
|
|
281
|
+
// it DELETES build/ before compiling, so a truncated attempt is not a no-op, it is
|
|
282
|
+
// destructive. Measured on a tree whose compiled binding opened a DB — 20 s cap, SIGTERM at
|
|
283
|
+
// 20.02 s, no `.node` left, `DB opens: YES` → `NO`, repeating on every SessionStart because
|
|
284
|
+
// setup.sh writes its marker only on success. Callers with a time budget must opt out and
|
|
285
|
+
// leave the compile to a foreground path (`rebuild-binding`, `healAndReexec`) that has none.
|
|
246
286
|
const sourceBuild =
|
|
247
|
-
deps.sourceBuild
|
|
248
|
-
|
|
287
|
+
deps.sourceBuild === false
|
|
288
|
+
? null
|
|
289
|
+
: deps.sourceBuild ||
|
|
290
|
+
(deps.rebuild
|
|
291
|
+
? null
|
|
292
|
+
: () => exec(NATIVE_BINDING_SOURCE_BUILD_CMD, { cwd: installDir, stdio: 'pipe' }));
|
|
249
293
|
if (!sourceBuild) return { ok: false, error: second.error || first.error };
|
|
250
294
|
|
|
251
295
|
try {
|
package/lib/install-shape.mjs
CHANGED
|
@@ -36,7 +36,7 @@ import { homedir } from 'node:os';
|
|
|
36
36
|
|
|
37
37
|
import {
|
|
38
38
|
probeBindingInFreshProcess,
|
|
39
|
-
|
|
39
|
+
nativeBindingRepairHint,
|
|
40
40
|
flattenBindingError,
|
|
41
41
|
} from './binding-probe.mjs';
|
|
42
42
|
|
|
@@ -245,7 +245,7 @@ export function probeRuntimeRoots(roots, deps = {}) {
|
|
|
245
245
|
// showed a bare path for the one fault family this check exists for.
|
|
246
246
|
const error = flattenBindingError(r.error);
|
|
247
247
|
return ownDeps
|
|
248
|
-
? { label, root, ok: false, error, repair:
|
|
248
|
+
? { label, root, ok: false, error, repair: nativeBindingRepairHint(root) }
|
|
249
249
|
: {
|
|
250
250
|
label,
|
|
251
251
|
root,
|