claude-mem-lite 2.83.2 → 2.84.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +18 -0
- package/README.zh-CN.md +18 -0
- package/cli.mjs +1 -1
- package/hook-update.mjs +50 -12
- package/hooks/hooks.json +7 -7
- package/install.mjs +62 -10
- package/package.json +2 -1
- package/scripts/hook-launcher.mjs +123 -0
- package/scripts/post-tool-use.sh +3 -2
- package/source-files.mjs +5 -0
package/README.md
CHANGED
|
@@ -498,6 +498,24 @@ Checks Node.js version, dependencies, server/hook files, database integrity, FTS
|
|
|
498
498
|
|
|
499
499
|
Shows MCP registration, hook configuration, plugin disabled state, and database stats (observation/session counts).
|
|
500
500
|
|
|
501
|
+
### Recovery (stuck install / hook errors)
|
|
502
|
+
|
|
503
|
+
If you see `ERR_MODULE_NOT_FOUND` on PreToolUse:Read/Edit/Skill hooks, or `claude-mem-lite` commands crash with import errors, you're likely hit by a partial auto-update — the updater copied new scripts but missed a sibling `lib/*` file, breaking the hook chain (and the next auto-update that would have healed it).
|
|
504
|
+
|
|
505
|
+
**v2.84.0+** ships a `repair` subcommand that re-syncs from the latest GitHub release:
|
|
506
|
+
|
|
507
|
+
```bash
|
|
508
|
+
claude-mem-lite repair
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
**If `repair` itself fails** (the bin is older than v2.84.0, or the bin is also broken), run this one-liner — it pulls a fresh tarball into a temp dir and runs *that* tarball's `install.mjs`, bypassing every file on your disk:
|
|
512
|
+
|
|
513
|
+
```bash
|
|
514
|
+
T=$(mktemp -d) && curl -sL https://api.github.com/repos/sdsrss/claude-mem-lite/tarball | tar xz -C "$T" --strip-components=1 && node "$T/install.mjs" install
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
After it finishes, `~/.claude-mem-lite/` is back in sync with the latest release and `claude-mem-lite repair` is available for next time.
|
|
518
|
+
|
|
501
519
|
## Uninstall
|
|
502
520
|
|
|
503
521
|
```bash
|
package/README.zh-CN.md
CHANGED
|
@@ -460,6 +460,24 @@ npx claude-mem-lite doctor # 诊断问题
|
|
|
460
460
|
|
|
461
461
|
显示 MCP 注册状态、钩子配置、插件禁用状态和数据库统计(观察/会话数量)。
|
|
462
462
|
|
|
463
|
+
### 故障恢复(安装卡死 / hook 报错)
|
|
464
|
+
|
|
465
|
+
如果你看到 PreToolUse:Read/Edit/Skill hook 报 `ERR_MODULE_NOT_FOUND`,或者 `claude-mem-lite` 命令本身因为 import 错误崩溃,多半是被部分自动更新坑了——更新器复制了新脚本但漏了配套的 `lib/*` 文件,hook 链就此断掉(连下一次本可自愈的自动更新也跑不了)。
|
|
466
|
+
|
|
467
|
+
**v2.84.0+** 提供 `repair` 子命令,从 GitHub 最新 release 重新同步:
|
|
468
|
+
|
|
469
|
+
```bash
|
|
470
|
+
claude-mem-lite repair
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
**如果 `repair` 自己也跑不起来**(bin 比 v2.84.0 旧,或 bin 也坏了),用这条单行命令——它把最新 tarball 拉到临时目录、跑 *那份* tarball 里的 `install.mjs`,完全不依赖你磁盘上的任何文件:
|
|
474
|
+
|
|
475
|
+
```bash
|
|
476
|
+
T=$(mktemp -d) && curl -sL https://api.github.com/repos/sdsrss/claude-mem-lite/tarball | tar xz -C "$T" --strip-components=1 && node "$T/install.mjs" install
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
跑完之后,`~/.claude-mem-lite/` 就和最新 release 对齐,`claude-mem-lite repair` 下次再遇到类似问题也能直接用了。
|
|
480
|
+
|
|
463
481
|
## 卸载
|
|
464
482
|
|
|
465
483
|
```bash
|
package/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const CLI_COMMANDS = new Set(['search', 'recent', 'recall', 'get', 'timeline', 'save', 'stats', 'context', 'browse', 'citation-stats', 'delete', 'update', 'export', 'compress', 'maintain', 'optimize', 'fts-check', 'registry', 'import', 'import-jsonl', 'enrich', 'activity', 'adopt', 'unadopt', 'memdir-audit', 'defer', 'help']);
|
|
3
|
-
const INSTALL_COMMANDS = new Set(['install', 'uninstall', 'status', 'doctor', 'cleanup', 'cleanup-hooks', 'self-update', 'release']);
|
|
3
|
+
const INSTALL_COMMANDS = new Set(['install', 'uninstall', 'status', 'doctor', 'cleanup', 'cleanup-hooks', 'self-update', 'repair', 'release']);
|
|
4
4
|
|
|
5
5
|
const cmd = process.argv[2];
|
|
6
6
|
|
package/hook-update.mjs
CHANGED
|
@@ -5,10 +5,14 @@
|
|
|
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
7
|
import { join, dirname } from 'node:path';
|
|
8
|
+
import { pathToFileURL } from 'node:url';
|
|
8
9
|
import { tmpdir, homedir } from 'node:os';
|
|
9
10
|
import { DB_DIR } from './schema.mjs';
|
|
10
11
|
import { debugCatch, debugLog } from './utils.mjs';
|
|
11
|
-
|
|
12
|
+
// Local manifest is fallback only — the active manifest is loaded from the
|
|
13
|
+
// extracted tarball's own source-files.mjs inside installExtractedRelease.
|
|
14
|
+
// See loadReleaseManifest below.
|
|
15
|
+
import { SOURCE_FILES as LOCAL_SOURCE_FILES, HOOK_SCRIPT_FILES as LOCAL_HOOK_SCRIPT_FILES } from './source-files.mjs';
|
|
12
16
|
|
|
13
17
|
// ── Configuration ──────────────────────────────────────────
|
|
14
18
|
const GITHUB_REPO = 'sdsrss/claude-mem-lite';
|
|
@@ -192,11 +196,42 @@ export function getCurrentVersion() {
|
|
|
192
196
|
} catch { return '0.0.0'; }
|
|
193
197
|
}
|
|
194
198
|
|
|
195
|
-
// Source files imported from shared ./source-files.mjs so install.mjs and
|
|
196
|
-
// hook-update.mjs can never drift (see tests/source-files-sync.test.mjs).
|
|
197
199
|
// SWITCHABLE_PATHS = everything in SOURCE_FILES plus the recursive dirs that
|
|
198
|
-
// install.mjs copies as whole subtrees (scripts, registry, node_modules).
|
|
199
|
-
|
|
200
|
+
// install.mjs copies as whole subtrees (scripts, registry, node_modules). It's
|
|
201
|
+
// built per-call from the *tarball's* manifest, not the locally-imported one —
|
|
202
|
+
// see loadReleaseManifest comment for why.
|
|
203
|
+
function buildSwitchablePaths(sourceFiles) {
|
|
204
|
+
return [...sourceFiles, 'scripts', 'registry', 'node_modules'];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Load the SOURCE_FILES / HOOK_SCRIPT_FILES manifest from the *extracted
|
|
208
|
+
// tarball's* own source-files.mjs. Critical: the locally-imported
|
|
209
|
+
// LOCAL_SOURCE_FILES is frozen at install time, so any entry added in the
|
|
210
|
+
// release we're installing is invisible to the running update. Pre-fix
|
|
211
|
+
// (≤ v2.83.2) used LOCAL_SOURCE_FILES for both copyReleaseIntoStaging and
|
|
212
|
+
// SWITCHABLE_PATHS — v2.80.x → v2.81.0 auto-update copied the new hook.mjs
|
|
213
|
+
// (in the v2.80 manifest) but skipped lib/cite-back-hint.mjs (added in v2.81),
|
|
214
|
+
// breaking SessionStart on every machine that auto-updated and killing the
|
|
215
|
+
// hook chain that would otherwise self-heal on the next round.
|
|
216
|
+
async function loadReleaseManifest(sourceDir) {
|
|
217
|
+
const manifestPath = join(sourceDir, 'source-files.mjs');
|
|
218
|
+
if (!existsSync(manifestPath)) {
|
|
219
|
+
return { SOURCE_FILES: LOCAL_SOURCE_FILES, HOOK_SCRIPT_FILES: LOCAL_HOOK_SCRIPT_FILES, source: 'fallback-missing' };
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
const mod = await import(pathToFileURL(manifestPath).href + `?t=${Date.now()}`);
|
|
223
|
+
if (!Array.isArray(mod.SOURCE_FILES) || mod.SOURCE_FILES.length === 0) {
|
|
224
|
+
throw new Error('SOURCE_FILES missing or empty');
|
|
225
|
+
}
|
|
226
|
+
if (!Array.isArray(mod.HOOK_SCRIPT_FILES)) {
|
|
227
|
+
throw new Error('HOOK_SCRIPT_FILES missing');
|
|
228
|
+
}
|
|
229
|
+
return { SOURCE_FILES: mod.SOURCE_FILES, HOOK_SCRIPT_FILES: mod.HOOK_SCRIPT_FILES, source: 'tarball' };
|
|
230
|
+
} catch (e) {
|
|
231
|
+
debugCatch(e, 'loadReleaseManifest');
|
|
232
|
+
return { SOURCE_FILES: LOCAL_SOURCE_FILES, HOOK_SCRIPT_FILES: LOCAL_HOOK_SCRIPT_FILES, source: 'fallback-error' };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
200
235
|
|
|
201
236
|
// ── Download & Install ─────────────────────────────────────
|
|
202
237
|
// Direct file copy instead of running old install.mjs (avoids symlink overwrite in dev)
|
|
@@ -223,7 +258,7 @@ async function downloadAndInstall(tarballUrl, expectedVersion) {
|
|
|
223
258
|
return false;
|
|
224
259
|
}
|
|
225
260
|
|
|
226
|
-
return installExtractedRelease(tmpDir);
|
|
261
|
+
return await installExtractedRelease(tmpDir);
|
|
227
262
|
} catch (err) {
|
|
228
263
|
debugCatch(err, 'downloadAndInstall');
|
|
229
264
|
return false;
|
|
@@ -271,25 +306,28 @@ export function validateExtractedTarball(sourceDir, expectedVersion, expectedNam
|
|
|
271
306
|
return { ok: true };
|
|
272
307
|
}
|
|
273
308
|
|
|
274
|
-
export function installExtractedRelease(sourceDir, targetDir = INSTALL_DIR) {
|
|
309
|
+
export async function installExtractedRelease(sourceDir, targetDir = INSTALL_DIR) {
|
|
275
310
|
const ts = `${Date.now()}-${process.pid}`;
|
|
276
311
|
const stagingDir = join(targetDir, `.update-staging-${ts}`);
|
|
277
312
|
const backupDir = join(targetDir, `.update-backup-${ts}`);
|
|
278
313
|
const backedUp = [];
|
|
279
314
|
const installed = [];
|
|
280
315
|
|
|
316
|
+
const manifest = await loadReleaseManifest(sourceDir);
|
|
317
|
+
const switchablePaths = buildSwitchablePaths(manifest.SOURCE_FILES);
|
|
318
|
+
|
|
281
319
|
try {
|
|
282
320
|
mkdirSync(stagingDir, { recursive: true });
|
|
283
321
|
mkdirSync(backupDir, { recursive: true });
|
|
284
322
|
|
|
285
|
-
copyReleaseIntoStaging(sourceDir, stagingDir);
|
|
323
|
+
copyReleaseIntoStaging(sourceDir, stagingDir, manifest);
|
|
286
324
|
execSync(NPM_INSTALL_CMD, {
|
|
287
325
|
cwd: stagingDir,
|
|
288
326
|
timeout: 60000,
|
|
289
327
|
stdio: 'pipe',
|
|
290
328
|
});
|
|
291
329
|
|
|
292
|
-
for (const relPath of
|
|
330
|
+
for (const relPath of switchablePaths) {
|
|
293
331
|
const stagedPath = join(stagingDir, relPath);
|
|
294
332
|
if (!existsSync(stagedPath)) continue;
|
|
295
333
|
|
|
@@ -368,10 +406,10 @@ export function installExtractedRelease(sourceDir, targetDir = INSTALL_DIR) {
|
|
|
368
406
|
}
|
|
369
407
|
}
|
|
370
408
|
|
|
371
|
-
function copyReleaseIntoStaging(sourceDir, stagingDir) {
|
|
409
|
+
function copyReleaseIntoStaging(sourceDir, stagingDir, manifest = { SOURCE_FILES: LOCAL_SOURCE_FILES, HOOK_SCRIPT_FILES: LOCAL_HOOK_SCRIPT_FILES }) {
|
|
372
410
|
let copied = 0;
|
|
373
411
|
|
|
374
|
-
for (const f of SOURCE_FILES) {
|
|
412
|
+
for (const f of manifest.SOURCE_FILES) {
|
|
375
413
|
const src = join(sourceDir, f);
|
|
376
414
|
const dest = join(stagingDir, f);
|
|
377
415
|
if (!existsSync(src)) continue;
|
|
@@ -389,7 +427,7 @@ function copyReleaseIntoStaging(sourceDir, stagingDir) {
|
|
|
389
427
|
const sourceScripts = join(sourceDir, 'scripts');
|
|
390
428
|
if (existsSync(sourceScripts)) {
|
|
391
429
|
mkdirSync(stagingScripts, { recursive: true });
|
|
392
|
-
for (const name of HOOK_SCRIPT_FILES) {
|
|
430
|
+
for (const name of manifest.HOOK_SCRIPT_FILES) {
|
|
393
431
|
const src = join(sourceScripts, name);
|
|
394
432
|
if (existsSync(src)) copyFileSync(src, join(stagingScripts, name));
|
|
395
433
|
}
|
package/hooks/hooks.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
"type": "command",
|
|
15
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hook.mjs\" session-start",
|
|
15
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-launcher.mjs\" hook.mjs session-start",
|
|
16
16
|
"timeout": 15
|
|
17
17
|
}
|
|
18
18
|
]
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"hooks": [
|
|
25
25
|
{
|
|
26
26
|
"type": "command",
|
|
27
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hook.mjs\" pre-compact",
|
|
27
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-launcher.mjs\" hook.mjs pre-compact",
|
|
28
28
|
"timeout": 5
|
|
29
29
|
}
|
|
30
30
|
]
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"hooks": [
|
|
37
37
|
{
|
|
38
38
|
"type": "command",
|
|
39
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/pre-tool-recall.js
|
|
39
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-launcher.mjs\" scripts/pre-tool-recall.js",
|
|
40
40
|
"timeout": 3
|
|
41
41
|
}
|
|
42
42
|
]
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"hooks": [
|
|
47
47
|
{
|
|
48
48
|
"type": "command",
|
|
49
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/pre-skill-bridge.js
|
|
49
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-launcher.mjs\" scripts/pre-skill-bridge.js",
|
|
50
50
|
"timeout": 3
|
|
51
51
|
}
|
|
52
52
|
]
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"hooks": [
|
|
71
71
|
{
|
|
72
72
|
"type": "command",
|
|
73
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hook.mjs\" stop",
|
|
73
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-launcher.mjs\" hook.mjs stop",
|
|
74
74
|
"timeout": 5
|
|
75
75
|
}
|
|
76
76
|
]
|
|
@@ -82,12 +82,12 @@
|
|
|
82
82
|
"hooks": [
|
|
83
83
|
{
|
|
84
84
|
"type": "command",
|
|
85
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/user-prompt-search.js
|
|
85
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-launcher.mjs\" scripts/user-prompt-search.js",
|
|
86
86
|
"timeout": 2
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
89
|
"type": "command",
|
|
90
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hook.mjs\" user-prompt",
|
|
90
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-launcher.mjs\" hook.mjs user-prompt",
|
|
91
91
|
"timeout": 5
|
|
92
92
|
}
|
|
93
93
|
]
|
package/install.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { execSync, execFileSync } from 'child_process';
|
|
5
5
|
import { readFileSync, writeFileSync, existsSync, rmSync, mkdirSync, copyFileSync, cpSync, renameSync, symlinkSync, unlinkSync, readdirSync, statSync, lstatSync } from 'fs';
|
|
6
6
|
import { join, resolve, dirname, isAbsolute } from 'path';
|
|
7
|
-
import { homedir } from 'os';
|
|
7
|
+
import { homedir, tmpdir } from 'os';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
|
|
10
10
|
const PROJECT_DIR = resolve(import.meta.dirname ?? dirname(fileURLToPath(import.meta.url)));
|
|
@@ -553,7 +553,13 @@ async function install() {
|
|
|
553
553
|
}
|
|
554
554
|
settings.hooks = settings.hooks || {};
|
|
555
555
|
|
|
556
|
-
const
|
|
556
|
+
const SCRIPTS_PATH = join(INSTALL_DIR, 'scripts');
|
|
557
|
+
const PREFILTER_PATH = join(SCRIPTS_PATH, 'post-tool-use.sh');
|
|
558
|
+
// v2.84: every Node hook invocation routes through hook-launcher.mjs so an
|
|
559
|
+
// ERR_MODULE_NOT_FOUND from a partial-install drift auto-heals via
|
|
560
|
+
// install.mjs repair instead of permanently bricking the hook chain.
|
|
561
|
+
const LAUNCHER_PATH = join(SCRIPTS_PATH, 'hook-launcher.mjs');
|
|
562
|
+
const nodeHook = (entry, ...args) => `node "${LAUNCHER_PATH}" ${entry} ${args.join(' ')}`.trim();
|
|
557
563
|
|
|
558
564
|
const memPostToolUse = {
|
|
559
565
|
matcher: '*',
|
|
@@ -568,7 +574,7 @@ async function install() {
|
|
|
568
574
|
matcher: 'startup|clear|compact',
|
|
569
575
|
hooks: [{
|
|
570
576
|
type: 'command',
|
|
571
|
-
command:
|
|
577
|
+
command: nodeHook('hook.mjs', 'session-start'),
|
|
572
578
|
timeout: 10
|
|
573
579
|
}]
|
|
574
580
|
};
|
|
@@ -577,24 +583,22 @@ async function install() {
|
|
|
577
583
|
matcher: '*',
|
|
578
584
|
hooks: [{
|
|
579
585
|
type: 'command',
|
|
580
|
-
command:
|
|
586
|
+
command: nodeHook('hook.mjs', 'stop'),
|
|
581
587
|
timeout: 5
|
|
582
588
|
}]
|
|
583
589
|
};
|
|
584
590
|
|
|
585
|
-
const SCRIPTS_PATH = join(INSTALL_DIR, 'scripts');
|
|
586
|
-
|
|
587
591
|
const memUserPrompt = {
|
|
588
592
|
matcher: '*',
|
|
589
593
|
hooks: [
|
|
590
594
|
{
|
|
591
595
|
type: 'command',
|
|
592
|
-
command:
|
|
596
|
+
command: nodeHook('scripts/user-prompt-search.js'),
|
|
593
597
|
timeout: 2
|
|
594
598
|
},
|
|
595
599
|
{
|
|
596
600
|
type: 'command',
|
|
597
|
-
command:
|
|
601
|
+
command: nodeHook('hook.mjs', 'user-prompt'),
|
|
598
602
|
timeout: 5
|
|
599
603
|
}
|
|
600
604
|
]
|
|
@@ -608,7 +612,7 @@ async function install() {
|
|
|
608
612
|
hooks: [
|
|
609
613
|
{
|
|
610
614
|
type: 'command',
|
|
611
|
-
command:
|
|
615
|
+
command: nodeHook('scripts/pre-tool-recall.js'),
|
|
612
616
|
timeout: 3
|
|
613
617
|
}
|
|
614
618
|
]
|
|
@@ -619,7 +623,7 @@ async function install() {
|
|
|
619
623
|
hooks: [
|
|
620
624
|
{
|
|
621
625
|
type: 'command',
|
|
622
|
-
command:
|
|
626
|
+
command: nodeHook('scripts/pre-skill-bridge.js'),
|
|
623
627
|
timeout: 3
|
|
624
628
|
}
|
|
625
629
|
]
|
|
@@ -1758,6 +1762,50 @@ async function manualUpdate() {
|
|
|
1758
1762
|
console.log('');
|
|
1759
1763
|
}
|
|
1760
1764
|
|
|
1765
|
+
// ─── Repair: Re-sync from latest GitHub Release ─────────────────────────────
|
|
1766
|
+
// Recovery path for installs broken by a partial auto-update (most often the
|
|
1767
|
+
// stale-manifest bug fixed in v2.84.0: hook-update.mjs copied the new hook.mjs
|
|
1768
|
+
// but skipped a new lib/* entry, leaving an ERR_MODULE_NOT_FOUND that
|
|
1769
|
+
// permanently disables the hook chain — including the next auto-update that
|
|
1770
|
+
// would have healed it). Self-contained: downloads a fresh tarball and spawns
|
|
1771
|
+
// the tarball's own install.mjs install, so the recovery path always runs the
|
|
1772
|
+
// latest code even when local install.mjs / hook-update.mjs are themselves
|
|
1773
|
+
// buggy on disk.
|
|
1774
|
+
async function repair() {
|
|
1775
|
+
console.log('\nclaude-mem-lite repair — re-syncing from latest GitHub release\n');
|
|
1776
|
+
const stagingDir = join(tmpdir(), `claude-mem-lite-repair-${Date.now()}`);
|
|
1777
|
+
mkdirSync(stagingDir, { recursive: true });
|
|
1778
|
+
try {
|
|
1779
|
+
const tarballUrl = 'https://api.github.com/repos/sdsrss/claude-mem-lite/tarball';
|
|
1780
|
+
const tarballPath = join(stagingDir, 'release.tgz');
|
|
1781
|
+
log('Downloading latest release tarball...');
|
|
1782
|
+
execFileSync('curl', ['-sL', '-f', '-H', 'Accept: application/vnd.github+json', tarballUrl, '-o', tarballPath],
|
|
1783
|
+
{ timeout: 60000, stdio: ['ignore', 'pipe', 'inherit'] });
|
|
1784
|
+
log('Extracting...');
|
|
1785
|
+
execFileSync('tar', ['xzf', tarballPath, '-C', stagingDir, '--strip-components=1'],
|
|
1786
|
+
{ timeout: 30000, stdio: ['ignore', 'pipe', 'inherit'] });
|
|
1787
|
+
const tarballInstaller = join(stagingDir, 'install.mjs');
|
|
1788
|
+
if (!existsSync(tarballInstaller)) {
|
|
1789
|
+
fail('Tarball missing install.mjs — repair aborted');
|
|
1790
|
+
process.exit(1);
|
|
1791
|
+
}
|
|
1792
|
+
log('Re-running install from freshly-downloaded sources...');
|
|
1793
|
+
execFileSync(process.execPath, [tarballInstaller, 'install'],
|
|
1794
|
+
{ stdio: 'inherit', timeout: 300000 });
|
|
1795
|
+
ok('Repair complete — broken install resynced from latest release');
|
|
1796
|
+
} catch (e) {
|
|
1797
|
+
fail(`Repair failed: ${e.message}`);
|
|
1798
|
+
console.log('');
|
|
1799
|
+
console.log(' Manual fallback — run this in any shell:');
|
|
1800
|
+
console.log('');
|
|
1801
|
+
console.log(' T=$(mktemp -d) && curl -sL https://api.github.com/repos/sdsrss/claude-mem-lite/tarball | tar xz -C "$T" --strip-components=1 && node "$T/install.mjs" install');
|
|
1802
|
+
console.log('');
|
|
1803
|
+
process.exit(1);
|
|
1804
|
+
} finally {
|
|
1805
|
+
try { rmSync(stagingDir, { recursive: true, force: true }); } catch {}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1761
1809
|
// ─── Release: Sync Versions ─────────────────────────────────────────────────
|
|
1762
1810
|
|
|
1763
1811
|
function syncVersions() {
|
|
@@ -1860,6 +1908,9 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
1860
1908
|
case 'update':
|
|
1861
1909
|
await manualUpdate();
|
|
1862
1910
|
break;
|
|
1911
|
+
case 'repair':
|
|
1912
|
+
await repair();
|
|
1913
|
+
break;
|
|
1863
1914
|
case 'release':
|
|
1864
1915
|
syncVersions();
|
|
1865
1916
|
if (!flags.has('--no-lock')) regenerateLockfile();
|
|
@@ -1889,6 +1940,7 @@ Usage:
|
|
|
1889
1940
|
node install.mjs cleanup Remove stale temp/staging files (use --dry-run to preview)
|
|
1890
1941
|
node install.mjs cleanup-hooks Remove only claude-mem-lite hooks from settings.json
|
|
1891
1942
|
node install.mjs self-update Check for and install updates
|
|
1943
|
+
node install.mjs repair Recover a broken install: download latest tarball, re-run install
|
|
1892
1944
|
node install.mjs release Sync versions (plugin/marketplace/CLAUDE.md) + regen lockfile via npm@10.9.2 (use --no-lock to skip lock regen)
|
|
1893
1945
|
|
|
1894
1946
|
npx claude-mem-lite Install via npx (one-liner)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.84.1",
|
|
4
4
|
"description": "Lightweight persistent memory system for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@10.9.2",
|
|
@@ -119,6 +119,7 @@
|
|
|
119
119
|
"scripts/pre-tool-recall.js",
|
|
120
120
|
"scripts/pre-skill-bridge.js",
|
|
121
121
|
"scripts/prompt-search-utils.mjs",
|
|
122
|
+
"scripts/hook-launcher.mjs",
|
|
122
123
|
".mcp.json",
|
|
123
124
|
".claude-plugin/plugin.json",
|
|
124
125
|
".claude-plugin/marketplace.json",
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/hook-launcher.mjs — Self-healing wrapper for Node hook entry points.
|
|
3
|
+
//
|
|
4
|
+
// Why: pre-v2.84 a stale-manifest bug in hook-update.mjs could leave the
|
|
5
|
+
// install with a hook.mjs that imports lib/cite-back-hint.mjs (or any other
|
|
6
|
+
// newly-added module) while the file itself was never copied. The resulting
|
|
7
|
+
// ERR_MODULE_NOT_FOUND killed every hook fire, including the next auto-update
|
|
8
|
+
// that would have healed the install. v2.84.0 fixes the root cause; this
|
|
9
|
+
// launcher is defense-in-depth for similar future drift (corrupt download,
|
|
10
|
+
// half-applied install, manual file deletion).
|
|
11
|
+
//
|
|
12
|
+
// Behavior: try-import the target entry. On ERR_MODULE_NOT_FOUND whose URL
|
|
13
|
+
// points under the install dir, run `install.mjs repair` (rate-limited via a
|
|
14
|
+
// 6h marker file under runtime/) and retry the import once. On any other
|
|
15
|
+
// exception, re-throw so Node's default error surface is preserved.
|
|
16
|
+
//
|
|
17
|
+
// HARD constraint: pure node: imports only. Importing anything from lib/ here
|
|
18
|
+
// would defeat the entire purpose — the launcher must survive a broken
|
|
19
|
+
// install.
|
|
20
|
+
|
|
21
|
+
import { existsSync, mkdirSync, writeFileSync, statSync } from 'node:fs';
|
|
22
|
+
import { spawnSync } from 'node:child_process';
|
|
23
|
+
import { dirname, join } from 'node:path';
|
|
24
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
25
|
+
import { homedir } from 'node:os';
|
|
26
|
+
|
|
27
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const INSTALL_DIR = join(__dirname, '..');
|
|
29
|
+
const RUNTIME_DIR = process.env.CLAUDE_MEM_DIR
|
|
30
|
+
? join(process.env.CLAUDE_MEM_DIR, 'runtime')
|
|
31
|
+
: join(homedir(), '.claude-mem-lite', 'runtime');
|
|
32
|
+
const HEAL_MARKER = join(RUNTIME_DIR, 'hook-launcher-lastheal');
|
|
33
|
+
const HEAL_COOLDOWN_MS = 6 * 60 * 60 * 1000;
|
|
34
|
+
|
|
35
|
+
// Last-resort recovery string for users whose `claude-mem-lite repair` path
|
|
36
|
+
// itself failed (install.mjs missing / repair errored / retry still drifting).
|
|
37
|
+
// Duplicated in install.mjs::repair() catch; both are reachable when local
|
|
38
|
+
// scripts are broken, so neither can import a shared constant.
|
|
39
|
+
const TARBALL_FALLBACK =
|
|
40
|
+
'T=$(mktemp -d) && curl -sL https://api.github.com/repos/sdsrss/claude-mem-lite/tarball | tar xz -C "$T" --strip-components=1 && node "$T/install.mjs" install';
|
|
41
|
+
|
|
42
|
+
const [, , entryArg, ...rest] = process.argv;
|
|
43
|
+
if (!entryArg) {
|
|
44
|
+
process.stderr.write('[claude-mem-lite] hook-launcher: missing entry argument\n');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const entryAbs = entryArg.startsWith('/') ? entryArg : join(INSTALL_DIR, entryArg);
|
|
49
|
+
|
|
50
|
+
async function runEntry({ bustCache = false } = {}) {
|
|
51
|
+
// Mirror direct invocation: process.argv[1] is the entry, [2..] are its args.
|
|
52
|
+
process.argv = [process.argv[0], entryAbs, ...rest];
|
|
53
|
+
// Node ESM caches resolution outcomes (success AND failure) by URL. On the
|
|
54
|
+
// post-self-heal retry the freshly-written module file lives at the same
|
|
55
|
+
// path the first import already cached as ERR_MODULE_NOT_FOUND — without a
|
|
56
|
+
// cache-buster query the second await import() returns the cached rejection
|
|
57
|
+
// and the heal looks like it did nothing.
|
|
58
|
+
const url = pathToFileURL(entryAbs).href + (bustCache ? `?t=${Date.now()}` : '');
|
|
59
|
+
await import(url);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isLocalModuleErr(e) {
|
|
63
|
+
if (!e || e.code !== 'ERR_MODULE_NOT_FOUND') return false;
|
|
64
|
+
const where = String(e.url || e.message || '');
|
|
65
|
+
return where.includes('.claude-mem-lite') || where.startsWith(`file://${INSTALL_DIR}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function recentHealAttempt() {
|
|
69
|
+
try {
|
|
70
|
+
return Date.now() - statSync(HEAL_MARKER).mtimeMs < HEAL_COOLDOWN_MS;
|
|
71
|
+
} catch { return false; }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function recordHealAttempt() {
|
|
75
|
+
try {
|
|
76
|
+
mkdirSync(RUNTIME_DIR, { recursive: true });
|
|
77
|
+
writeFileSync(HEAL_MARKER, String(Date.now()));
|
|
78
|
+
} catch { /* best-effort */ }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function attemptHeal(reason) {
|
|
82
|
+
if (recentHealAttempt()) {
|
|
83
|
+
process.stderr.write(
|
|
84
|
+
`[claude-mem-lite] Self-heal skipped (last attempt < 6h ago).\n` +
|
|
85
|
+
`[claude-mem-lite] Manual recovery: claude-mem-lite repair\n` +
|
|
86
|
+
`[claude-mem-lite] If that fails, run: ${TARBALL_FALLBACK}\n`,
|
|
87
|
+
);
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
recordHealAttempt();
|
|
91
|
+
process.stderr.write(`[claude-mem-lite] Detected broken install (${reason}) — running self-heal\n`);
|
|
92
|
+
const installer = join(INSTALL_DIR, 'install.mjs');
|
|
93
|
+
if (!existsSync(installer)) {
|
|
94
|
+
process.stderr.write(
|
|
95
|
+
`[claude-mem-lite] install.mjs missing at ${installer} — cannot self-heal\n` +
|
|
96
|
+
`[claude-mem-lite] Manual recovery: ${TARBALL_FALLBACK}\n`,
|
|
97
|
+
);
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
const result = spawnSync(process.execPath, [installer, 'repair'], {
|
|
101
|
+
stdio: 'inherit',
|
|
102
|
+
timeout: 300000,
|
|
103
|
+
});
|
|
104
|
+
return result.status === 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
await runEntry();
|
|
109
|
+
} catch (e) {
|
|
110
|
+
if (!isLocalModuleErr(e)) throw e;
|
|
111
|
+
const reason = String(e.url || e.message).split('/').slice(-2).join('/');
|
|
112
|
+
const healed = await attemptHeal(reason);
|
|
113
|
+
if (!healed) throw e;
|
|
114
|
+
try {
|
|
115
|
+
await runEntry({ bustCache: true });
|
|
116
|
+
} catch (retryErr) {
|
|
117
|
+
process.stderr.write(
|
|
118
|
+
`[claude-mem-lite] Hook still failing after self-heal: ${retryErr.message}\n` +
|
|
119
|
+
`[claude-mem-lite] Manual recovery: ${TARBALL_FALLBACK}\n`,
|
|
120
|
+
);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
package/scripts/post-tool-use.sh
CHANGED
|
@@ -56,6 +56,7 @@ case "$tool" in
|
|
|
56
56
|
;;
|
|
57
57
|
esac
|
|
58
58
|
|
|
59
|
-
# Tool not skipped — hand off to Node for full processing
|
|
59
|
+
# Tool not skipped — hand off to Node for full processing.
|
|
60
|
+
# Routed through hook-launcher.mjs (self-heal on ERR_MODULE_NOT_FOUND).
|
|
60
61
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" || exit 1
|
|
61
|
-
printf '%s' "$input" | node "${SCRIPT_DIR}/hook.mjs" post-tool-use
|
|
62
|
+
printf '%s' "$input" | node "${SCRIPT_DIR}/scripts/hook-launcher.mjs" hook.mjs post-tool-use
|
package/source-files.mjs
CHANGED
|
@@ -110,4 +110,9 @@ export const HOOK_SCRIPT_FILES = [
|
|
|
110
110
|
'prompt-search-utils.mjs',
|
|
111
111
|
'pre-tool-recall.js',
|
|
112
112
|
'pre-skill-bridge.js',
|
|
113
|
+
// v2.84: self-heal wrapper that detects ERR_MODULE_NOT_FOUND under the
|
|
114
|
+
// install dir and runs install.mjs repair before retrying the entry.
|
|
115
|
+
// hooks.json + install.mjs settings template invoke node hook entries
|
|
116
|
+
// through this wrapper so any partial-install drift heals automatically.
|
|
117
|
+
'hook-launcher.mjs',
|
|
113
118
|
];
|