claude-mem-lite 6.22.0 → 6.22.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 +14 -0
- package/README.zh-CN.md +8 -0
- package/adopt-cli.mjs +12 -0
- package/install.mjs +47 -7
- package/lib/atomic-write.mjs +26 -0
- package/lib/local-steering.mjs +18 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/scripts/hook-launcher.mjs +6 -10
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"plugins": [
|
|
10
10
|
{
|
|
11
11
|
"name": "claude-mem-lite",
|
|
12
|
-
"version": "6.22.
|
|
12
|
+
"version": "6.22.1",
|
|
13
13
|
"source": "./",
|
|
14
14
|
"homepage": "https://github.com/sdsrss/claude-mem-lite",
|
|
15
15
|
"description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. FTS5 BM25 keyword 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)."
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "6.22.
|
|
3
|
+
"version": "6.22.1",
|
|
4
4
|
"description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. FTS5 BM25 keyword 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
|
@@ -534,6 +534,20 @@ surface — reach them through the CLI column in the second table.
|
|
|
534
534
|
| `mem_fts_check` | `claude-mem-lite fts-check <check\|rebuild>` | FTS5 integrity + rebuild. |
|
|
535
535
|
| `mem_browse` | `claude-mem-lite browse` | Tier-grouped dashboard (working / active / archive). |
|
|
536
536
|
|
|
537
|
+
**Headless runs (`claude -p`).** With no permission prompt to answer, Claude Code 2.1.292
|
|
538
|
+
refused every `mem_*` call until the server's tools were allowed, and the model did not switch
|
|
539
|
+
to the CLI by itself: in an 88-session headless run (2026-10-06) all 13 calls were refused and
|
|
540
|
+
nothing was saved. On 2.1.284 (2026-09-29) the same flags let these calls through, so this
|
|
541
|
+
depends on the Claude Code version. Allow the server once in the `settings.json` the run reads:
|
|
542
|
+
|
|
543
|
+
```json
|
|
544
|
+
{ "permissions": { "allow": ["mcp__plugin_claude-mem-lite_mem-lite"] } }
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
That is the server's name under a plugin install. An npx or git-clone install registers it as
|
|
548
|
+
`mem-lite`, so the rule there is `mcp__mem-lite`. An interactive session asks instead; answering
|
|
549
|
+
"always allow" there records a rule of its own.
|
|
550
|
+
|
|
537
551
|
### Skill Commands (in Claude Code chat)
|
|
538
552
|
|
|
539
553
|
```
|
package/README.zh-CN.md
CHANGED
|
@@ -439,6 +439,14 @@ README 和 `docs/ARCHITECTURE.md` 都钉在它上面。)
|
|
|
439
439
|
| `mem_fts_check` | `claude-mem-lite fts-check <check\|rebuild>` | FTS5 完整性检查与重建。 |
|
|
440
440
|
| `mem_browse` | `claude-mem-lite browse` | 分层仪表盘(working / active / archive)。 |
|
|
441
441
|
|
|
442
|
+
**无人值守运行(`claude -p`)。** 没有权限确认可答时,Claude Code 2.1.292 拒绝了所有 `mem_*` 调用,直到服务的工具被放行;模型也没有自己改用 CLI。在 88 个无人值守会话里(2026-10-06),13 次调用全部被拒,什么都没存下。在 2.1.284 上(2026-09-29),同样的参数会放行这些调用,所以这取决于 Claude Code 版本。在这次运行读取的 `settings.json` 里放行一次即可:
|
|
443
|
+
|
|
444
|
+
```json
|
|
445
|
+
{ "permissions": { "allow": ["mcp__plugin_claude-mem-lite_mem-lite"] } }
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
这是插件安装方式下的服务名。npx 或 git clone 安装方式把服务注册为 `mem-lite`,对应的规则是 `mcp__mem-lite`。交互式会话会弹出确认;在那里选"始终允许"会另外记下一条规则。
|
|
449
|
+
|
|
442
450
|
### 技能命令(在 Claude Code 聊天中使用)
|
|
443
451
|
|
|
444
452
|
```
|
package/adopt-cli.mjs
CHANGED
|
@@ -62,6 +62,7 @@ import {
|
|
|
62
62
|
forgetLocalSteering,
|
|
63
63
|
localSteeringRemembered,
|
|
64
64
|
agentsMdForNewClaudeMd,
|
|
65
|
+
claudeMdSwitchesOffAgentsBelow,
|
|
65
66
|
tildePath,
|
|
66
67
|
RULES_MD,
|
|
67
68
|
RULES_REFUSAL_TEXT,
|
|
@@ -190,6 +191,15 @@ function adoptOne(cwd, { force, dryRun }) {
|
|
|
190
191
|
(p) =>
|
|
191
192
|
` ⚠ ${p} ${verb} loading in sessions here once CLAUDE.md exists; set Project instructions to claude-md-and-agents-md in /config to keep it`,
|
|
192
193
|
);
|
|
194
|
+
// D#225: at $HOME or `/` the file is above every project there, which no list above can name.
|
|
195
|
+
// Written anyway (adopt was asked to), and said.
|
|
196
|
+
const switchesOffBelow = claudeMdSwitchesOffAgentsBelow(cwd, PLUGIN_SLUG);
|
|
197
|
+
const sharedNote = (verb) =>
|
|
198
|
+
switchesOffBelow
|
|
199
|
+
? [
|
|
200
|
+
` ⚠ ${claudeMdPath(cwd)} ${verb} Claude Code reading the AGENTS.md of every project below ${cwd} that has one; to steer one project, run adopt inside it instead${verb === 'stops' ? ' (`claude-mem-lite unadopt` here takes this back)' : ''}, or set Project instructions to claude-md-and-agents-md in /config`,
|
|
201
|
+
]
|
|
202
|
+
: [];
|
|
193
203
|
|
|
194
204
|
if (dryRun) {
|
|
195
205
|
log(`[adopt --dry-run] ${cwd}`);
|
|
@@ -198,6 +208,7 @@ function adoptOne(cwd, { force, dryRun }) {
|
|
|
198
208
|
if (imports.length > 0)
|
|
199
209
|
log(` AGENTS.md: would import ${imports.join(', ')} at the top of CLAUDE.md`);
|
|
200
210
|
for (const line of elsewhere('would stop')) log(line);
|
|
211
|
+
for (const line of sharedNote('would stop')) log(line);
|
|
201
212
|
if (hasLegacyMemdirSentinel(cwd, PLUGIN_SLUG)) {
|
|
202
213
|
log(` legacy migrate: would strip memory-dir sentinel @ ${memdirPath(cwd)}`);
|
|
203
214
|
}
|
|
@@ -224,6 +235,7 @@ function adoptOne(cwd, { force, dryRun }) {
|
|
|
224
235
|
: ` (+removed the block from ${local.path})`;
|
|
225
236
|
log(`[adopt] ${cwd} → ${r.action}${migNote}${localNote}${importNote}`);
|
|
226
237
|
for (const line of elsewhere('stops')) log(line);
|
|
238
|
+
for (const line of sharedNote('stops')) log(line);
|
|
227
239
|
// The copy that could not come out loads beside CLAUDE.md: said, and not a success (R3-4).
|
|
228
240
|
if (local.action === 'failed') {
|
|
229
241
|
if (local.residue) log(` ⚠ ${local.residue}`);
|
package/install.mjs
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
rmSync,
|
|
11
11
|
mkdirSync,
|
|
12
12
|
mkdtempSync,
|
|
13
|
-
copyFileSync,
|
|
14
13
|
renameSync,
|
|
15
14
|
symlinkSync,
|
|
16
15
|
unlinkSync,
|
|
@@ -98,7 +97,7 @@ import { detectInstallShape, probeRuntimeRoots, hasAnyManagedCode } from './lib/
|
|
|
98
97
|
import { probeSchemaCompat, schemaSkewRemedy } from './lib/schema-skew.mjs';
|
|
99
98
|
import { clearNativeBindingBreakage, readNativeBindingBreakage } from './lib/native-binding-hint.mjs';
|
|
100
99
|
import { acquireLock } from './lib/proc-lock.mjs';
|
|
101
|
-
import { atomicWriteFileSync } from './lib/atomic-write.mjs';
|
|
100
|
+
import { atomicWriteFileSync, atomicCopyFileSync } from './lib/atomic-write.mjs';
|
|
102
101
|
import { shellWord } from './cli-path.mjs';
|
|
103
102
|
import { claudeStatePath } from './lib/data-paths.mjs';
|
|
104
103
|
import { isMemHook, isMemHookCommand, stripMemHooks, launcherEntryPath } from './lib/hook-prune.mjs';
|
|
@@ -118,7 +117,8 @@ export { probeBetterSqlite3Binding, ensureBetterSqlite3Working };
|
|
|
118
117
|
export function copyHookScripts(srcDir, destDir) {
|
|
119
118
|
for (const name of HOOK_SCRIPT_FILES) {
|
|
120
119
|
const src = join(srcDir, name);
|
|
121
|
-
|
|
120
|
+
// By rename, not in place: hooks import these while install runs (D#223).
|
|
121
|
+
if (existsSync(src)) atomicCopyFileSync(src, join(destDir, name));
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
@@ -454,7 +454,38 @@ const importFromInstall = (rel) => import(pathToFileURL(join(INSTALL_DIR, rel)).
|
|
|
454
454
|
const requireFromInstall = createRequire(pathToFileURL(join(INSTALL_DIR, 'package.json')).href);
|
|
455
455
|
|
|
456
456
|
// ─── install() step helpers (audit P1-9) ──────────────────────────────────────
|
|
457
|
-
|
|
457
|
+
|
|
458
|
+
// Swap barrier (R10 P2-12, D#223). Hooks import this code tree on every tool call, and
|
|
459
|
+
// `install` — also what a background `repair` ends in — rewrites it while a session runs.
|
|
460
|
+
// hook-update.mjs arms this same marker around its renames and scripts/hook-launcher.mjs
|
|
461
|
+
// skips a fire while it is present, so a fire that starts mid-deploy is dropped instead of
|
|
462
|
+
// importing a mix of two versions. The marker carries pid + ts because the launcher ignores
|
|
463
|
+
// one whose writer is gone or which is older than two minutes. Callers hold install.lock,
|
|
464
|
+
// which hook-update's swap takes too, so the two never arm it at once.
|
|
465
|
+
const SWAP_MARKER = join(MEM_DATA_DIR, 'runtime', 'swap-in-progress'); // runtime-dir:stays-put — installation identity; the launcher reads it here
|
|
466
|
+
|
|
467
|
+
export async function withSwapBarrier(fn) {
|
|
468
|
+
try {
|
|
469
|
+
mkdirSync(dirname(SWAP_MARKER), { recursive: true });
|
|
470
|
+
writeFileSync(SWAP_MARKER, JSON.stringify({ pid: process.pid, ts: Date.now() }));
|
|
471
|
+
} catch {
|
|
472
|
+
/* best-effort: without it hooks fire unbarriered, as before */
|
|
473
|
+
}
|
|
474
|
+
try {
|
|
475
|
+
return await fn();
|
|
476
|
+
} finally {
|
|
477
|
+
try {
|
|
478
|
+
rmSync(SWAP_MARKER, { force: true });
|
|
479
|
+
} catch {
|
|
480
|
+
/* a marker left behind expires with its writer */
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Creates the data dirs the swap barrier then writes its marker into. The legacy-dir migration
|
|
486
|
+
// here runs only under CLAUDE_MEM_DIR relocation: in the default shape runLockedInstall's
|
|
487
|
+
// install.lock has created ~/.claude-mem-lite before install() starts (pre-existing, D#238).
|
|
488
|
+
function prepareInstallDirs() {
|
|
458
489
|
// Auto-migrate unhidden dir (~/claude-mem-lite/ → ~/.claude-mem-lite/)
|
|
459
490
|
const oldUnhidden = join(homedir(), 'claude-mem-lite');
|
|
460
491
|
if (!existsSync(DATA_DIR) && existsSync(oldUnhidden)) {
|
|
@@ -466,7 +497,9 @@ function installSourceFiles(IS_DEV) {
|
|
|
466
497
|
if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
|
|
467
498
|
// Under relocation the DB/managed/runtime live here, not in the code dir — create it too.
|
|
468
499
|
if (!existsSync(MEM_DATA_DIR)) mkdirSync(MEM_DATA_DIR, { recursive: true });
|
|
500
|
+
}
|
|
469
501
|
|
|
502
|
+
export function deployCodeTree(IS_DEV) {
|
|
470
503
|
if (IS_DEV) {
|
|
471
504
|
log('Dev mode — creating symlinks in ~/.claude-mem-lite/...');
|
|
472
505
|
// Symlink individual source files
|
|
@@ -512,7 +545,7 @@ function installSourceFiles(IS_DEV) {
|
|
|
512
545
|
// Ensure parent dir exists for subdir entries (e.g. 'lib/activity.mjs')
|
|
513
546
|
const dstParent = dirname(dst);
|
|
514
547
|
if (!existsSync(dstParent)) mkdirSync(dstParent, { recursive: true });
|
|
515
|
-
|
|
548
|
+
atomicCopyFileSync(src, dst); // by rename, not in place (D#223)
|
|
516
549
|
}
|
|
517
550
|
}
|
|
518
551
|
// Copy hook scripts (settings.json hook commands point at these — must
|
|
@@ -1250,8 +1283,15 @@ async function install() {
|
|
|
1250
1283
|
// 1. Install source files to ~/.claude-mem-lite/
|
|
1251
1284
|
const IS_DEV = flags.has('--dev');
|
|
1252
1285
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1286
|
+
prepareInstallDirs();
|
|
1287
|
+
// Both steps rewrite the tree hooks import — the code, then node_modules — so one swap
|
|
1288
|
+
// barrier spans them (D#223), for its first two minutes: the launcher ignores an older marker,
|
|
1289
|
+
// and npm install has no timeout. installDependencies may process.exit(1): the marker it leaves
|
|
1290
|
+
// names a dead pid, which the launcher ignores.
|
|
1291
|
+
await withSwapBarrier(async () => {
|
|
1292
|
+
deployCodeTree(IS_DEV);
|
|
1293
|
+
await installDependencies(IS_DEV);
|
|
1294
|
+
});
|
|
1255
1295
|
createCliSymlink();
|
|
1256
1296
|
registerMcpServer();
|
|
1257
1297
|
// configureHooks BEFORE dedupe, and its result feeds the dedup gate: dedupe now
|
package/lib/atomic-write.mjs
CHANGED
|
@@ -95,3 +95,29 @@ export function atomicWriteFileSync(filePath, data, { backup = false, mode: newF
|
|
|
95
95
|
throw e;
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Replace `dst` with a copy of `src` by rename, so a concurrent reader sees the old file or
|
|
101
|
+
* the new one, never a truncated one. copyFileSync onto a live file opens it with O_TRUNC and
|
|
102
|
+
* then writes: a hook importing it in between gets an empty or half-written module (D#223).
|
|
103
|
+
* Unlike atomicWriteFileSync this does NOT write through a symlink at `dst`: the targets are
|
|
104
|
+
* installed code files, where a link left by a dev-mode install must be replaced, not used to
|
|
105
|
+
* overwrite the developer's checkout. The source's mode bits come along (copyFileSync keeps
|
|
106
|
+
* them), so an executable hook script stays executable.
|
|
107
|
+
* @param {string} src
|
|
108
|
+
* @param {string} dst
|
|
109
|
+
*/
|
|
110
|
+
export function atomicCopyFileSync(src, dst) {
|
|
111
|
+
const tmp = `${dst}.tmp-${process.pid}`;
|
|
112
|
+
try {
|
|
113
|
+
copyFileSync(src, tmp);
|
|
114
|
+
renameSync(tmp, dst);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
try {
|
|
117
|
+
unlinkSync(tmp);
|
|
118
|
+
} catch {
|
|
119
|
+
/* nothing written, or already gone */
|
|
120
|
+
}
|
|
121
|
+
throw e;
|
|
122
|
+
}
|
|
123
|
+
}
|
package/lib/local-steering.mjs
CHANGED
|
@@ -382,6 +382,24 @@ export function agentsMdForNewClaudeMd(dir, slug) {
|
|
|
382
382
|
return AGENTS_MD_UNAFFECTED.has(userInstructionFiles()) ? null : { imports, elsewhere };
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Whether a CLAUDE.md written in `dir` switches AGENTS.md off in every project below it, beyond the
|
|
387
|
+
* files agentsMdForNewClaudeMd can list (D#225): `dir` is $HOME or `/`, no CLAUDE.md or
|
|
388
|
+
* CLAUDE.local.md of the user's at or above it has done so already, and Project instructions is not
|
|
389
|
+
* a value that reads AGENTS.md regardless.
|
|
390
|
+
* @param {string} dir where CLAUDE.md is written
|
|
391
|
+
* @param {string} slug
|
|
392
|
+
* @returns {boolean}
|
|
393
|
+
*/
|
|
394
|
+
export function claudeMdSwitchesOffAgentsBelow(dir, slug) {
|
|
395
|
+
const d = resolve(dir);
|
|
396
|
+
return (
|
|
397
|
+
isSharedAncestor(d) &&
|
|
398
|
+
!instructionFileAtOrAbove(d, slug) &&
|
|
399
|
+
!AGENTS_MD_UNAFFECTED.has(userInstructionFiles())
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
385
403
|
function instructionFileAtOrAbove(start, slug) {
|
|
386
404
|
const home = realOrResolved(homedir());
|
|
387
405
|
const usersFile = (p) => existsSync(p) && !holdsOnlyOwnLines(p, slug);
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "6.22.
|
|
3
|
+
"version": "6.22.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "claude-mem-lite",
|
|
9
|
-
"version": "6.22.
|
|
9
|
+
"version": "6.22.1",
|
|
10
10
|
"os": [
|
|
11
11
|
"darwin",
|
|
12
12
|
"linux",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "6.22.
|
|
3
|
+
"version": "6.22.1",
|
|
4
4
|
"description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. FTS5 BM25 keyword 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",
|
|
@@ -155,16 +155,12 @@ const entryAbs = entryArg.startsWith('/') ? entryArg : join(INSTALL_DIR, entryAr
|
|
|
155
155
|
// module graph. Hooks are best-effort and the swap lasts ~a second, so the next
|
|
156
156
|
// fire runs against a settled install.
|
|
157
157
|
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
// keep firing into it. That is R10 P2-12, still open: the mechanism is settled, the runtime
|
|
165
|
-
// symptom is not reproduced, and R10 §8 asks for a repro in tests/sandbox/phaseB-npm.mjs
|
|
166
|
-
// before install()'s main path is touched. An earlier draft of this comment said "auto-update
|
|
167
|
-
// / repair", which reads as though the repair path were already covered — it is not.
|
|
158
|
+
// Two writers arm it: hook-update.mjs around its renames, and install.mjs's install()
|
|
159
|
+
// around deploying the code tree and its npm install — also what `install.mjs repair`
|
|
160
|
+
// ends in, which SessionStart can spawn in the background while hooks keep firing. The
|
|
161
|
+
// install side came late (R10 P2-12, D#223): it copied in place with copyFileSync, and
|
|
162
|
+
// tests/sandbox/phaseB-npm.mjs B10 caught a fire importing a truncated module on a
|
|
163
|
+
// same-version re-install. Its copies now go through a rename as well.
|
|
168
164
|
//
|
|
169
165
|
// Stale-guarded on BOTH pid and ts: an updater killed mid-swap leaves the marker
|
|
170
166
|
// behind, and a marker that outlives its writer must never mute hooks permanently.
|