claude-mem-lite 2.78.0 → 2.79.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 +10 -0
- package/README.zh-CN.md +10 -0
- package/hook.mjs +28 -1
- package/install.mjs +66 -2
- package/package.json +1 -1
- package/scripts/setup.sh +48 -6
- package/server.mjs +7 -1
package/README.md
CHANGED
|
@@ -138,6 +138,8 @@ The original sends **everything to the LLM and hopes it filters well**. claude-m
|
|
|
138
138
|
|
|
139
139
|
Plugin mode manages its own hooks/runtime. On session start it only **checks and reports** new claude-mem-lite versions; it does **not** self-overwrite plugin files in place. Update plugin-mode installs through Claude's plugin workflow.
|
|
140
140
|
|
|
141
|
+
> **First session per project: run `/adopt` once.** Plugin install gives you the MCP server + hooks + slash commands, but the **invited-memory sentinel** (a system-authority pointer that boosts Claude's proactive use of `mem_recall` / `mem_save`) is opt-in and project-scoped. Without it the hooks still record observations and inject context, but Claude is far less likely to call the MCP tools on its own. One-time per project: `/adopt`. Remove with `/unadopt`.
|
|
142
|
+
|
|
141
143
|
### Method 2: npx (one-liner)
|
|
142
144
|
|
|
143
145
|
```bash
|
|
@@ -511,6 +513,14 @@ Data in `~/.claude-mem-lite/` is preserved by default. Delete manually if needed
|
|
|
511
513
|
rm -rf ~/.claude-mem-lite/
|
|
512
514
|
```
|
|
513
515
|
|
|
516
|
+
### Mixed-install residue (read this if you've used multiple install methods)
|
|
517
|
+
|
|
518
|
+
`/plugin uninstall` only removes the plugin manifest — it **does not touch `~/.claude/settings.json`**. If you've ever run `claude-mem-lite install` (npx or git-clone path), hook entries pointing at `~/.claude-mem-lite/hook.mjs` were written into your user-global settings, and they keep firing after `/plugin uninstall`. If `~/.claude-mem-lite/hook.mjs` still exists they double-fire alongside the plugin; if you also ran `rm -rf ~/.claude-mem-lite/` they error every session.
|
|
519
|
+
|
|
520
|
+
**The safe sequence is**: run `claude-mem-lite uninstall` first (which cleans the settings.json hooks plus the global MCP registration), then `/plugin uninstall claude-mem-lite`, then optionally `rm -rf ~/.claude-mem-lite/`.
|
|
521
|
+
|
|
522
|
+
If you already uninstalled in the wrong order, `claude-mem-lite doctor` flags orphan hooks under `Orphan hooks:` with the exact cleanup command.
|
|
523
|
+
|
|
514
524
|
## Project Structure
|
|
515
525
|
|
|
516
526
|
```
|
package/README.zh-CN.md
CHANGED
|
@@ -147,6 +147,8 @@ node install.mjs install
|
|
|
147
147
|
|
|
148
148
|
1. **安装依赖** -- `npm install --omit=dev`(编译原生 `better-sqlite3`)
|
|
149
149
|
2. **注册 MCP 服务器** -- `mem-lite` 服务器,包含 15 个工具(search、recent、recall、timeline、get、save、update、stats、delete、compress、maintain、export、fts_check、browse、registry)。v2.78 前服务器名为通用的 `mem`,现已改名为 `mem-lite` 避免与用户其它 `.mcp.json` 冲突;工具名(`mem_search`/`mem_recall` 等)保持不变。
|
|
150
|
+
|
|
151
|
+
> **每个项目第一次使用,跑一次 `/adopt`。** Plugin 安装给你 MCP server + hooks + slash commands,但**邀请式 memory 哨兵**(一条提升 Claude 主动调用 `mem_recall` / `mem_save` 的 system-authority 指针)是按项目 opt-in 的。不跑 `/adopt` 时 hooks 仍记录观察、注入上下文,但 Claude 不太会主动调 MCP 工具。一次性、按项目:`/adopt`;撤销 `/unadopt`。
|
|
150
152
|
3. **配置钩子** -- `PostToolUse`、`PreToolUse`、`SessionStart`、`Stop`、`UserPromptSubmit` 生命周期钩子
|
|
151
153
|
4. **创建数据目录** -- `~/.claude-mem-lite/`(隐藏目录),存放数据库、运行时和托管资源文件
|
|
152
154
|
5. **自动迁移** -- 自动检测 `~/.claude-mem/`(原版 claude-mem)或 `~/claude-mem-lite/`(v0.5 前的非隐藏目录),将数据库和运行时文件迁移到 `~/.claude-mem-lite/`,原目录保持不变
|
|
@@ -474,6 +476,14 @@ npx claude-mem-lite uninstall --purge
|
|
|
474
476
|
rm -rf ~/.claude-mem-lite/
|
|
475
477
|
```
|
|
476
478
|
|
|
479
|
+
### 混装残留(用过多种安装方式的话务必看一下)
|
|
480
|
+
|
|
481
|
+
`/plugin uninstall` 只删 plugin manifest,**不会动 `~/.claude/settings.json`**。如果你曾经跑过 `claude-mem-lite install`(npx 或 git-clone 路径),指向 `~/.claude-mem-lite/hook.mjs` 的 hook 条目就被写进了你的 user-global settings;`/plugin uninstall` 之后它们还在每会话触发。如果 `~/.claude-mem-lite/hook.mjs` 还在 → 与 plugin 双触发;如果你又 `rm -rf ~/.claude-mem-lite/` → 每次会话报错。
|
|
482
|
+
|
|
483
|
+
**正确顺序**:先 `claude-mem-lite uninstall`(清 settings.json 的 hook + 全局 MCP 注册),再 `/plugin uninstall claude-mem-lite`,最后可选 `rm -rf ~/.claude-mem-lite/`。
|
|
484
|
+
|
|
485
|
+
顺序搞错了的话,`claude-mem-lite doctor` 会在 `Orphan hooks:` 一节标出残留并给清理命令。
|
|
486
|
+
|
|
477
487
|
## 项目结构
|
|
478
488
|
|
|
479
489
|
```
|
package/hook.mjs
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
import { randomUUID } from 'crypto';
|
|
22
22
|
import { join } from 'path';
|
|
23
|
-
import { readFileSync, writeFileSync, unlinkSync, readdirSync, renameSync, statSync } from 'fs';
|
|
23
|
+
import { readFileSync, writeFileSync, unlinkSync, readdirSync, renameSync, statSync, existsSync } from 'fs';
|
|
24
24
|
import { homedir } from 'os';
|
|
25
25
|
import {
|
|
26
26
|
truncate, inferProject, detectBashSignificance,
|
|
@@ -1115,6 +1115,33 @@ async function handleSessionStart() {
|
|
|
1115
1115
|
if (citeNudge) {
|
|
1116
1116
|
dashboardText = dashboardText ? `${citeNudge}\n${dashboardText}` : citeNudge;
|
|
1117
1117
|
}
|
|
1118
|
+
// v2.79: surface setup.sh dependency-install failure as a high-visibility
|
|
1119
|
+
// line at the very top of the dashboard. setup.sh writes runtime/.deps-broken
|
|
1120
|
+
// (JSON: ts/reason/root/repair) on failure and removes it on success — so
|
|
1121
|
+
// a stale flag self-heals on the next clean SessionStart. Without this
|
|
1122
|
+
// surface, hook degradation looks identical to "nothing happening" until
|
|
1123
|
+
// the user notices missing context days later.
|
|
1124
|
+
try {
|
|
1125
|
+
const depsFlag = join(RUNTIME_DIR, '.deps-broken');
|
|
1126
|
+
if (existsSync(depsFlag)) {
|
|
1127
|
+
let detail = 'unknown';
|
|
1128
|
+
let repair = '';
|
|
1129
|
+
try {
|
|
1130
|
+
const raw = readFileSync(depsFlag, 'utf8').trim();
|
|
1131
|
+
const parsed = JSON.parse(raw);
|
|
1132
|
+
detail = parsed.reason || detail;
|
|
1133
|
+
repair = parsed.repair || '';
|
|
1134
|
+
} catch { /* corrupt flag — surface the fact only */ }
|
|
1135
|
+
const nudgeLines = [
|
|
1136
|
+
'⚠️ [claude-mem-lite] Hook dependencies failed to install on the last SessionStart.',
|
|
1137
|
+
` Reason: ${detail}`,
|
|
1138
|
+
];
|
|
1139
|
+
if (repair) nudgeLines.push(` Repair: ${repair}`);
|
|
1140
|
+
nudgeLines.push(' Until fixed, PreToolUse / PostToolUse / memory injection are degraded.');
|
|
1141
|
+
const nudge = nudgeLines.join('\n');
|
|
1142
|
+
dashboardText = dashboardText ? `${nudge}\n${dashboardText}` : nudge;
|
|
1143
|
+
}
|
|
1144
|
+
} catch (e) { debugCatch(e, 'session-start-deps-flag'); }
|
|
1118
1145
|
if (dashboardText) {
|
|
1119
1146
|
process.stdout.write(JSON.stringify({
|
|
1120
1147
|
suppressOutput: true,
|
package/install.mjs
CHANGED
|
@@ -1086,8 +1086,12 @@ async function status() {
|
|
|
1086
1086
|
// Accept either the current "mem-lite" registration or the legacy "mem"
|
|
1087
1087
|
// name (pre-v2.78) so a user mid-upgrade still sees a green status until
|
|
1088
1088
|
// setup.sh / install.mjs purges the legacy entry on next run.
|
|
1089
|
-
|
|
1090
|
-
|
|
1089
|
+
// v2.79.1: dropped a `/\bmem\b\s/` fallback regex — the `\b` word boundary
|
|
1090
|
+
// also matched "mem-lite" (because `-` is a non-word char), so the regex
|
|
1091
|
+
// was always-true noise (benign only because the mem-lite checks short-
|
|
1092
|
+
// circuited first). `claude mcp list` formats as `<name>: <command>`, so
|
|
1093
|
+
// the two colon-form checks below cover every shape.
|
|
1094
|
+
const registered = list.includes('mem-lite:') || list.includes('mem:');
|
|
1091
1095
|
push(registered ? 'ok' : 'fail', 'mcp', registered ? 'MCP server: registered' : 'MCP server: not registered', { registered });
|
|
1092
1096
|
} catch {
|
|
1093
1097
|
push('warn', 'mcp', 'Could not check MCP status', { registered: null });
|
|
@@ -1261,6 +1265,24 @@ async function doctor() {
|
|
|
1261
1265
|
dwarn('Plugin lifecycle: hooks not configured');
|
|
1262
1266
|
}
|
|
1263
1267
|
|
|
1268
|
+
// Orphan hooks: settings.json entries referencing hook files that no longer
|
|
1269
|
+
// exist on disk. Trips when a user runs `/plugin uninstall` and/or
|
|
1270
|
+
// `rm -rf ~/.claude-mem-lite/` without first running `claude-mem-lite uninstall`
|
|
1271
|
+
// (which clears the settings.json entries). The hooks keep firing and exit
|
|
1272
|
+
// with require-error noise every session. README's Uninstall section warns
|
|
1273
|
+
// about the right ordering; this check flags the broken state so it surfaces
|
|
1274
|
+
// even when the user skipped the README.
|
|
1275
|
+
const orphanPaths = collectOrphanHookPaths(settings);
|
|
1276
|
+
if (orphanPaths.length > 0) {
|
|
1277
|
+
fail(`Orphan hooks: ${orphanPaths.length} settings.json entr${orphanPaths.length === 1 ? 'y references a missing file' : 'ies reference missing files'}`);
|
|
1278
|
+
for (const p of orphanPaths.slice(0, 5)) log(` missing: ${p}`);
|
|
1279
|
+
if (orphanPaths.length > 5) log(` ... +${orphanPaths.length - 5} more`);
|
|
1280
|
+
log(` Repair: node ${join(PROJECT_DIR, 'install.mjs')} uninstall # removes the dead hook entries`);
|
|
1281
|
+
issues++;
|
|
1282
|
+
} else if (hasHooks) {
|
|
1283
|
+
ok('Orphan hooks: none (all hook targets present)');
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1264
1286
|
// Database
|
|
1265
1287
|
if (existsSync(DB_PATH)) {
|
|
1266
1288
|
try {
|
|
@@ -1483,6 +1505,48 @@ function hasMemHooksConfigured(settings) {
|
|
|
1483
1505
|
);
|
|
1484
1506
|
}
|
|
1485
1507
|
|
|
1508
|
+
/**
|
|
1509
|
+
* Walk every mem-hook command in settings.json and collect any absolute file
|
|
1510
|
+
* paths that don't currently exist on disk. Used by doctor() to surface
|
|
1511
|
+
* post-uninstall residue ("/plugin uninstall claude-mem-lite" leaves
|
|
1512
|
+
* settings.json hooks pointing at ~/.claude-mem-lite/hook.mjs; if the user
|
|
1513
|
+
* then deleted that directory, every session start dispatches to a missing
|
|
1514
|
+
* file).
|
|
1515
|
+
*
|
|
1516
|
+
* Path extraction: command strings look like:
|
|
1517
|
+
* node "/home/sds/.claude-mem-lite/hook.mjs" session-start
|
|
1518
|
+
* bash "/home/sds/.claude-mem-lite/scripts/post-tool-use.sh"
|
|
1519
|
+
* node "/home/sds/.claude-mem-lite/scripts/pre-tool-recall.js"
|
|
1520
|
+
* We pick the first quoted absolute path; if there is no quoted token we fall
|
|
1521
|
+
* back to the first whitespace-delimited absolute-looking token after the
|
|
1522
|
+
* interpreter. ${CLAUDE_PLUGIN_ROOT}-templated commands are ignored — those
|
|
1523
|
+
* are plugin-owned hooks resolved by Claude Code at runtime, not by us.
|
|
1524
|
+
*/
|
|
1525
|
+
export function collectOrphanHookPaths(settings) {
|
|
1526
|
+
if (!settings?.hooks) return [];
|
|
1527
|
+
const out = [];
|
|
1528
|
+
for (const configs of Object.values(settings.hooks)) {
|
|
1529
|
+
if (!Array.isArray(configs)) continue;
|
|
1530
|
+
for (const cfg of configs) {
|
|
1531
|
+
if (!isMemHook(cfg)) continue;
|
|
1532
|
+
for (const h of cfg.hooks || []) {
|
|
1533
|
+
const cmd = h.command || '';
|
|
1534
|
+
if (cmd.includes('${CLAUDE_PLUGIN_ROOT}')) continue;
|
|
1535
|
+
const quoted = cmd.match(/"([^"]+)"/);
|
|
1536
|
+
let path = quoted ? quoted[1] : null;
|
|
1537
|
+
if (!path) {
|
|
1538
|
+
// unquoted: split on whitespace, take the first arg that looks like an absolute path
|
|
1539
|
+
const parts = cmd.split(/\s+/);
|
|
1540
|
+
path = parts.find(p => p.startsWith('/') && (p.endsWith('.mjs') || p.endsWith('.js') || p.endsWith('.sh'))) || null;
|
|
1541
|
+
}
|
|
1542
|
+
if (!path) continue;
|
|
1543
|
+
if (!existsSync(path) && !out.includes(path)) out.push(path);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
return out;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1486
1550
|
/**
|
|
1487
1551
|
* v2.48 P1-4: prune top-level stale files left behind by removed-module upgrades.
|
|
1488
1552
|
*
|
package/package.json
CHANGED
package/scripts/setup.sh
CHANGED
|
@@ -69,11 +69,46 @@ mkdir -p "$DATA_DIR/runtime"
|
|
|
69
69
|
|
|
70
70
|
# 6. Ensure native dependencies available for hooks (ESM import needs node_modules in resolution chain)
|
|
71
71
|
# Plugin cache doesn't include node_modules — symlink from data dir or npm install on first run
|
|
72
|
+
#
|
|
73
|
+
# Visibility contract: `npm install` failure here used to be a stderr-only log_warn,
|
|
74
|
+
# invisible to the Claude session unless the operator was watching the terminal.
|
|
75
|
+
# When it fails (no toolchain, blocked network, read-only FS) every hook silently
|
|
76
|
+
# degrades — pre-tool-recall, post-tool-use, session-start all import better-sqlite3
|
|
77
|
+
# and exit on the require() error. v2.79: write a JSON flag to runtime/.deps-broken
|
|
78
|
+
# and hook.mjs SessionStart surfaces it in the Claude context as a HIGH-VISIBILITY
|
|
79
|
+
# block; success branches remove the flag so a self-heal stays visible too.
|
|
80
|
+
DEPS_FLAG="$DATA_DIR/runtime/.deps-broken"
|
|
81
|
+
mkdir -p "$DATA_DIR/runtime" 2>/dev/null || true
|
|
82
|
+
|
|
83
|
+
mark_deps_broken() {
|
|
84
|
+
local reason="$1"
|
|
85
|
+
# Embed reason + repair command so hook.mjs renders a complete error without
|
|
86
|
+
# having to re-derive them. Delegate JSON serialization to node so embedded
|
|
87
|
+
# quotes / shell metachars in $ROOT or $reason can't produce an invalid file
|
|
88
|
+
# (bash `printf '"..%s.."'` cannot escape arbitrary strings safely; v2.79 fix).
|
|
89
|
+
MARK_REASON="$reason" MARK_ROOT="$ROOT" MARK_FLAG="$DEPS_FLAG" node -e '
|
|
90
|
+
const fs = require("fs");
|
|
91
|
+
const reason = process.env.MARK_REASON || "unknown";
|
|
92
|
+
const root = process.env.MARK_ROOT || "";
|
|
93
|
+
fs.writeFileSync(process.env.MARK_FLAG, JSON.stringify({
|
|
94
|
+
ts: new Date().toISOString(),
|
|
95
|
+
reason,
|
|
96
|
+
root,
|
|
97
|
+
repair: `cd ${JSON.stringify(root)} && npm install --omit=dev`,
|
|
98
|
+
}) + "\n");
|
|
99
|
+
' 2>/dev/null || true
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
mark_deps_ok() {
|
|
103
|
+
rm -f "$DEPS_FLAG" 2>/dev/null || true
|
|
104
|
+
}
|
|
105
|
+
|
|
72
106
|
if [[ ! -d "$ROOT/node_modules/better-sqlite3" ]]; then
|
|
73
107
|
# Fast path: symlink from data dir (instant, no network needed)
|
|
74
108
|
if [[ -d "$DATA_DIR/node_modules/better-sqlite3" ]]; then
|
|
75
109
|
if ln -sfn "$DATA_DIR/node_modules" "$ROOT/node_modules" 2>/dev/null; then
|
|
76
110
|
log_ok "Dependencies linked from $DATA_DIR"
|
|
111
|
+
mark_deps_ok
|
|
77
112
|
fi
|
|
78
113
|
fi
|
|
79
114
|
# Slow path: npm install (first-time only, ~10-20s for native addon)
|
|
@@ -81,24 +116,31 @@ if [[ ! -d "$ROOT/node_modules/better-sqlite3" ]]; then
|
|
|
81
116
|
log_info "Installing dependencies (first-time setup)..."
|
|
82
117
|
if (cd "$ROOT" && npm install --omit=dev --no-audit --no-fund 2>&1) >&2; then
|
|
83
118
|
log_ok "Dependencies installed"
|
|
119
|
+
mark_deps_ok
|
|
84
120
|
else
|
|
85
|
-
log_warn "Dependency install failed — hooks may have limited functionality"
|
|
121
|
+
log_warn "Dependency install failed — hooks may have limited functionality (flag: $DEPS_FLAG)"
|
|
122
|
+
mark_deps_broken "npm install --omit=dev failed in plugin cache root"
|
|
86
123
|
fi
|
|
87
124
|
fi
|
|
125
|
+
else
|
|
126
|
+
# Deps already present — make sure we don't keep stale broken flag around
|
|
127
|
+
mark_deps_ok
|
|
88
128
|
fi
|
|
89
129
|
|
|
90
|
-
# 7. MCP cleanup:
|
|
91
|
-
# This runs on every plugin SessionStart because old global entries may still
|
|
92
|
-
# exist even after an earlier migration marker was written.
|
|
130
|
+
# 7. MCP cleanup: one-shot purge of stale global MCP registrations.
|
|
93
131
|
# - Pre-2.10: direct installs left a global "mem" MCP alongside plugin MCP.
|
|
94
132
|
# - Pre-2.78: plugin and global registrations used the generic name "mem";
|
|
95
133
|
# v2.78 renamed to "mem-lite" — any stale global "mem" must be purged so
|
|
96
134
|
# Claude Code doesn't surface duplicate (old "mem" + new "mem-lite") tool
|
|
97
135
|
# prefixes side-by-side.
|
|
98
136
|
# Root .mcp.json in the installed plugin cache is required for Claude Code to
|
|
99
|
-
# register plugin MCP; only stale global/marketplace copies
|
|
137
|
+
# register plugin MCP; only stale global/marketplace copies are removed.
|
|
138
|
+
# v2.79.1: marker file now actually gates entry (was touched but never read
|
|
139
|
+
# pre-v2.79.1 — extra node spawn + JSON parse on every SessionStart for a
|
|
140
|
+
# near-always no-op). Bump MCP_MIGRATION name to re-run cleanup in future
|
|
141
|
+
# versions; same shape as the .deps-broken self-heal pattern.
|
|
100
142
|
MCP_MIGRATION="$DATA_DIR/runtime/.mcp-dedup-v2.78"
|
|
101
|
-
if [[ -n "${CLAUDE_PLUGIN_ROOT:-}" ]]; then
|
|
143
|
+
if [[ -n "${CLAUDE_PLUGIN_ROOT:-}" && ! -f "$MCP_MIGRATION" ]]; then
|
|
102
144
|
CLAUDE_JSON="$HOME/.claude.json" node -e '
|
|
103
145
|
const fs = require("fs");
|
|
104
146
|
let changed = false;
|
package/server.mjs
CHANGED
|
@@ -2272,6 +2272,12 @@ process.on('unhandledRejection', (err) => { debugCatch(err, 'unhandledRejection'
|
|
|
2272
2272
|
// server in one Claude Code session). Two records with close timestamps and
|
|
2273
2273
|
// the same ppid is the smoking gun. Never throws — telemetry must not block
|
|
2274
2274
|
// startup. Disable with MEM_DISABLE_SPAWN_LOG=1.
|
|
2275
|
+
//
|
|
2276
|
+
// File mode: no explicit chmod. Payload is `{ts, pid, ppid, argv1, version}` —
|
|
2277
|
+
// no secrets, no project content. Pre-v2.79.1 passed `{mode: 0o600}` to
|
|
2278
|
+
// appendFileSync but that only applies on file creation (umask-default after
|
|
2279
|
+
// the first append), so the "0600" claim was misleading honesty-wise. Honest
|
|
2280
|
+
// comment > misleading code.
|
|
2275
2281
|
if (process.env.MEM_DISABLE_SPAWN_LOG !== '1') {
|
|
2276
2282
|
try {
|
|
2277
2283
|
if (!existsSync(RUNTIME_DIR)) mkdirSync(RUNTIME_DIR, { recursive: true });
|
|
@@ -2282,7 +2288,7 @@ if (process.env.MEM_DISABLE_SPAWN_LOG !== '1') {
|
|
|
2282
2288
|
argv1: process.argv[1] || '',
|
|
2283
2289
|
version: PKG_VERSION,
|
|
2284
2290
|
}) + '\n';
|
|
2285
|
-
appendFileSync(join(RUNTIME_DIR, 'mcp-spawns.log'), line
|
|
2291
|
+
appendFileSync(join(RUNTIME_DIR, 'mcp-spawns.log'), line);
|
|
2286
2292
|
} catch { /* never block startup on telemetry failure */ }
|
|
2287
2293
|
}
|
|
2288
2294
|
|