claude-mem-lite 2.79.0 → 2.80.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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/install.mjs +25 -7
- package/lib/citation-tracker.mjs +11 -2
- package/package.json +1 -1
- package/scripts/setup.sh +21 -12
- package/server.mjs +7 -1
package/README.md
CHANGED
|
@@ -161,7 +161,7 @@ Source files stay in the cloned repo. Update via `git pull && node install.mjs i
|
|
|
161
161
|
### What happens during installation
|
|
162
162
|
|
|
163
163
|
1. **Install dependencies** -- `npm install --omit=dev` (compiles native `better-sqlite3`)
|
|
164
|
-
2. **Register MCP server** -- `mem-lite` server with
|
|
164
|
+
2. **Register MCP server** -- `mem-lite` server with 20 tools (9 core exposed via `tools/list` + 11 hidden-but-callable; see the Usage section for the full table). The pre-v2.78 generic server name `mem` is renamed to `mem-lite` for namespace hygiene; the tool names themselves (`mem_search`, `mem_recall`, ...) are unchanged.
|
|
165
165
|
3. **Configure hooks** -- `PostToolUse`, `SessionStart`, `Stop`, `UserPromptSubmit` lifecycle hooks
|
|
166
166
|
4. **Create data directory** -- `~/.claude-mem-lite/` (hidden) for database, runtime, and managed resource files
|
|
167
167
|
5. **Auto-migrate** -- If `~/.claude-mem/` (original claude-mem) or `~/claude-mem-lite/` (pre-v0.5 unhidden) exists, migrates database and runtime files to `~/.claude-mem-lite/`, preserving the original untouched
|
package/README.zh-CN.md
CHANGED
|
@@ -146,7 +146,7 @@ node install.mjs install
|
|
|
146
146
|
### 安装过程
|
|
147
147
|
|
|
148
148
|
1. **安装依赖** -- `npm install --omit=dev`(编译原生 `better-sqlite3`)
|
|
149
|
-
2. **注册 MCP 服务器** -- `mem-lite` 服务器,包含
|
|
149
|
+
2. **注册 MCP 服务器** -- `mem-lite` 服务器,包含 20 个工具(9 个核心通过 `tools/list` 暴露 + 11 个隐藏但可调;完整表见 Usage 段)。v2.78 前服务器名为通用的 `mem`,现已改名为 `mem-lite` 避免与用户其它 `.mcp.json` 冲突;工具名(`mem_search`/`mem_recall` 等)保持不变。
|
|
150
150
|
|
|
151
151
|
> **每个项目第一次使用,跑一次 `/adopt`。** Plugin 安装给你 MCP server + hooks + slash commands,但**邀请式 memory 哨兵**(一条提升 Claude 主动调用 `mem_recall` / `mem_save` 的 system-authority 指针)是按项目 opt-in 的。不跑 `/adopt` 时 hooks 仍记录观察、注入上下文,但 Claude 不太会主动调 MCP 工具。一次性、按项目:`/adopt`;撤销 `/unadopt`。
|
|
152
152
|
3. **配置钩子** -- `PostToolUse`、`PreToolUse`、`SessionStart`、`Stop`、`UserPromptSubmit` 生命周期钩子
|
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 });
|
|
@@ -1270,7 +1274,7 @@ async function doctor() {
|
|
|
1270
1274
|
// even when the user skipped the README.
|
|
1271
1275
|
const orphanPaths = collectOrphanHookPaths(settings);
|
|
1272
1276
|
if (orphanPaths.length > 0) {
|
|
1273
|
-
fail(`Orphan hooks: ${orphanPaths.length} settings.json entr${orphanPaths.length === 1 ? 'y references a' : 'ies reference
|
|
1277
|
+
fail(`Orphan hooks: ${orphanPaths.length} settings.json entr${orphanPaths.length === 1 ? 'y references a missing file' : 'ies reference missing files'}`);
|
|
1274
1278
|
for (const p of orphanPaths.slice(0, 5)) log(` missing: ${p}`);
|
|
1275
1279
|
if (orphanPaths.length > 5) log(` ... +${orphanPaths.length - 5} more`);
|
|
1276
1280
|
log(` Repair: node ${join(PROJECT_DIR, 'install.mjs')} uninstall # removes the dead hook entries`);
|
|
@@ -1518,6 +1522,13 @@ function hasMemHooksConfigured(settings) {
|
|
|
1518
1522
|
* interpreter. ${CLAUDE_PLUGIN_ROOT}-templated commands are ignored — those
|
|
1519
1523
|
* are plugin-owned hooks resolved by Claude Code at runtime, not by us.
|
|
1520
1524
|
*/
|
|
1525
|
+
const HOOK_PATH_EXTS = ['.mjs', '.js', '.cjs', '.sh'];
|
|
1526
|
+
|
|
1527
|
+
function looksLikeHookPath(p) {
|
|
1528
|
+
if (!p || !p.startsWith('/')) return false;
|
|
1529
|
+
return HOOK_PATH_EXTS.some(ext => p.endsWith(ext));
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1521
1532
|
export function collectOrphanHookPaths(settings) {
|
|
1522
1533
|
if (!settings?.hooks) return [];
|
|
1523
1534
|
const out = [];
|
|
@@ -1528,12 +1539,19 @@ export function collectOrphanHookPaths(settings) {
|
|
|
1528
1539
|
for (const h of cfg.hooks || []) {
|
|
1529
1540
|
const cmd = h.command || '';
|
|
1530
1541
|
if (cmd.includes('${CLAUDE_PLUGIN_ROOT}')) continue;
|
|
1531
|
-
|
|
1532
|
-
|
|
1542
|
+
// v2.80: scan ALL quoted tokens (was: only the first), prefer ones
|
|
1543
|
+
// that look like a hook path. Fixes a footgun where a wrapper command
|
|
1544
|
+
// like `bash -c "some inline" "/real/path.sh"` would pick "some inline"
|
|
1545
|
+
// and flag a false orphan. If no quoted token looks like a path, fall
|
|
1546
|
+
// through to the unquoted scanner; if that also misses, skip the
|
|
1547
|
+
// entry — we'd rather under-report than false-flag.
|
|
1548
|
+
let path = null;
|
|
1549
|
+
for (const m of cmd.matchAll(/"([^"]+)"/g)) {
|
|
1550
|
+
if (looksLikeHookPath(m[1])) { path = m[1]; break; }
|
|
1551
|
+
}
|
|
1533
1552
|
if (!path) {
|
|
1534
|
-
// unquoted: split on whitespace, take the first arg that looks like an absolute path
|
|
1535
1553
|
const parts = cmd.split(/\s+/);
|
|
1536
|
-
path = parts.find(p =>
|
|
1554
|
+
path = parts.find(p => looksLikeHookPath(p)) || null;
|
|
1537
1555
|
}
|
|
1538
1556
|
if (!path) continue;
|
|
1539
1557
|
if (!existsSync(path) && !out.includes(path)) out.push(path);
|
package/lib/citation-tracker.mjs
CHANGED
|
@@ -283,8 +283,17 @@ export function hasMainThreadAssistantText(transcriptPath) {
|
|
|
283
283
|
if (!transcriptPath || !existsSync(transcriptPath)) return false;
|
|
284
284
|
let raw;
|
|
285
285
|
try { raw = readFileSync(transcriptPath, 'utf8'); } catch { return false; }
|
|
286
|
-
|
|
287
|
-
|
|
286
|
+
// Reverse-iterate so a turn that just produced text returns true on the
|
|
287
|
+
// FIRST line examined instead of walking the entire transcript. Common case
|
|
288
|
+
// (model wrote a paragraph → Stop fires) short-circuits in O(1) line parses;
|
|
289
|
+
// pathological case (no text anywhere) still walks all entries but that's
|
|
290
|
+
// the degenerate state we want false for anyway. v2.80 perf polish — the
|
|
291
|
+
// pre-v2.80 forward scan held the full transcript in memory and walked
|
|
292
|
+
// every line on the common case too.
|
|
293
|
+
const lines = raw.split('\n');
|
|
294
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
295
|
+
const line = lines[i];
|
|
296
|
+
if (!line || !line.trim()) continue;
|
|
288
297
|
let entry;
|
|
289
298
|
try { entry = JSON.parse(line); } catch { continue; }
|
|
290
299
|
if (entry.type !== 'assistant' || !entry.message) continue;
|
package/package.json
CHANGED
package/scripts/setup.sh
CHANGED
|
@@ -83,13 +83,20 @@ mkdir -p "$DATA_DIR/runtime" 2>/dev/null || true
|
|
|
83
83
|
mark_deps_broken() {
|
|
84
84
|
local reason="$1"
|
|
85
85
|
# Embed reason + repair command so hook.mjs renders a complete error without
|
|
86
|
-
# having to re-derive them.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
"
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
93
100
|
}
|
|
94
101
|
|
|
95
102
|
mark_deps_ok() {
|
|
@@ -120,18 +127,20 @@ else
|
|
|
120
127
|
mark_deps_ok
|
|
121
128
|
fi
|
|
122
129
|
|
|
123
|
-
# 7. MCP cleanup:
|
|
124
|
-
# This runs on every plugin SessionStart because old global entries may still
|
|
125
|
-
# exist even after an earlier migration marker was written.
|
|
130
|
+
# 7. MCP cleanup: one-shot purge of stale global MCP registrations.
|
|
126
131
|
# - Pre-2.10: direct installs left a global "mem" MCP alongside plugin MCP.
|
|
127
132
|
# - Pre-2.78: plugin and global registrations used the generic name "mem";
|
|
128
133
|
# v2.78 renamed to "mem-lite" — any stale global "mem" must be purged so
|
|
129
134
|
# Claude Code doesn't surface duplicate (old "mem" + new "mem-lite") tool
|
|
130
135
|
# prefixes side-by-side.
|
|
131
136
|
# Root .mcp.json in the installed plugin cache is required for Claude Code to
|
|
132
|
-
# 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.
|
|
133
142
|
MCP_MIGRATION="$DATA_DIR/runtime/.mcp-dedup-v2.78"
|
|
134
|
-
if [[ -n "${CLAUDE_PLUGIN_ROOT:-}" ]]; then
|
|
143
|
+
if [[ -n "${CLAUDE_PLUGIN_ROOT:-}" && ! -f "$MCP_MIGRATION" ]]; then
|
|
135
144
|
CLAUDE_JSON="$HOME/.claude.json" node -e '
|
|
136
145
|
const fs = require("fs");
|
|
137
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
|
|