claude-mem-lite 6.30.5 → 6.30.7

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.
@@ -9,7 +9,7 @@
9
9
  "plugins": [
10
10
  {
11
11
  "name": "claude-mem-lite",
12
- "version": "6.30.5",
12
+ "version": "6.30.7",
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.30.5",
3
+ "version": "6.30.7",
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
@@ -197,7 +197,7 @@ node install.mjs install
197
197
  2. **Register MCP server** -- `mem-lite` server with 19 tool definitions (10 core + 9 hidden-but-callable; `mem_search_feedback` is registered only when search telemetry is enabled). 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.
198
198
  3. **Configure hooks** -- all seven lifecycle events: `SessionStart`, `PreCompact`, `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `Stop`, `UserPromptSubmit`
199
199
  4. **Create data directory** -- `~/.claude-mem-lite/` (hidden) for database, runtime, and managed resource files
200
- 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
200
+ 5. **Auto-migrate** -- `~/.claude-mem/claude-mem.db` (original claude-mem) is moved into `~/.claude-mem-lite/` as a `.legacy-backup-<time>` file and a fresh database is created. A database left in `~/claude-mem-lite/` (the unhidden directory of versions before the first release, 2.0.0) is moved into the data directory at the first open, when the data directory holds no database, log, snapshot or kept copy of a claude-mem-lite store yet; nothing else in either directory is moved or removed
201
201
  6. **Initialize database** -- SQLite with WAL mode, FTS5 indexes created on first server start
202
202
 
203
203
  Restart Claude Code after installation to activate.
@@ -207,20 +207,21 @@ Restart Claude Code after installation to activate.
207
207
  All installation methods auto-detect and migrate from previous versions:
208
208
 
209
209
  **From claude-mem (original `~/.claude-mem/`):**
210
- - Copy `claude-mem.db` → `~/.claude-mem-lite/claude-mem-lite.db` (renamed)
211
- - Copy the `runtime/` directory
212
- - **Original `~/.claude-mem/` is preserved** (no deletion, no overwrite)
210
+ - `claude-mem.db` and its `-wal`/`-shm` are moved into `~/.claude-mem-lite/` under `.legacy-backup-<time>` names (`claude-mem-lite.db.legacy-backup-<time>`, `claude-mem-lite.db-wal.legacy-backup-<time>`), when `~/.claude-mem-lite/claude-mem-lite.db` does not exist yet
211
+ - The plugin install copies `claude-mem.db` alone into the data directory under the first of those names and leaves `~/.claude-mem/` as it is; it does so while the data directory holds neither a database nor such a backup
212
+ - A fresh database is created: the original claude-mem schema has no upgrade path
213
213
 
214
- **From pre-v0.5 unhidden directory (`~/claude-mem-lite/`):**
215
- - Entire directory is moved to `~/.claude-mem-lite/` (hidden)
214
+ **From the unhidden directory of versions before the first release, 2.0.0 (`~/claude-mem-lite/`):**
215
+ - Its database (`claude-mem-lite.db` or `claude-mem.db`, with its `-wal` and `-shm`) is moved into the data directory at the first open, when the data directory holds no database, log, snapshot or kept copy of a claude-mem-lite store yet. Nothing else is moved, and nothing in either directory is removed
216
+ - The move is a rename: with `CLAUDE_MEM_DIR` on another filesystem it fails, and so does every open, until the database is moved there by hand together with its `-wal` and `-shm`
216
217
 
217
218
  **In-place rename:**
218
219
  - Existing `claude-mem.db` in `~/.claude-mem-lite/` is automatically renamed to `claude-mem-lite.db`
219
220
 
220
- Remove old directories manually after confirming:
221
+ Remove old directories manually once nothing in them is needed. A database still in `~/claude-mem-lite/` has not moved (see above), and the command below deletes it:
221
222
  ```bash
222
223
  rm -rf ~/.claude-mem/ # original claude-mem
223
- rm -rf ~/claude-mem-lite/ # pre-v0.5 unhidden (if not auto-moved)
224
+ rm -rf ~/claude-mem-lite/ # unhidden data dir from before the first release (not a git clone of this repository)
224
225
  ```
225
226
 
226
227
  ### Directory Structure
@@ -1069,7 +1070,7 @@ claude-mem-lite/
1069
1070
  skill.md # MCP skill definition (npx/git clone mode)
1070
1071
  package.json # Dependencies and metadata
1071
1072
  scripts/
1072
- setup.sh # Setup hook: npm install + migration (hidden dir + old dir)
1073
+ setup.sh # Setup hook: npm install + migration (original claude-mem DB, old file name)
1073
1074
  post-tool-use.sh # Bash pre-filter: skips noise in ~5ms, tracks Read paths
1074
1075
  user-prompt-search.js # UserPromptSubmit hook: auto-search memory on user prompts
1075
1076
  pre-tool-recall.js # PreToolUse hook: file lesson recall before Edit/Write
package/README.zh-CN.md CHANGED
@@ -159,7 +159,7 @@ node install.mjs install
159
159
  > **想把托管块改写进 `CLAUDE.md`**(例如与团队共享)?运行 `claude-mem-lite adopt`:它向 `<cwd>/CLAUDE.md` 写入 slug 限定的托管块和详情文件,块以外的内容逐字保留,并删除自动写入的那份本地副本(`CLAUDE.local.md` 或 rules 文件)。`CLAUDE.md` 一存在,Claude Code 就不再读 `AGENTS.md`,所以 adopt 在 `AGENTS.md`(或 `.claude/AGENTS.md`)旁边新建 `CLAUDE.md` 时,会在文件开头导入它(`@AGENTS.md`,前面一行是 Claude Code 不加载的标记注释),`AGENTS.md` 因此继续加载;上层目录里的、或 git 跟踪在子目录里的 `AGENTS.md` 不能这样导入,adopt 会在输出里列出它们,并给出保留它们的设置(Project instructions 设为 `claude-md-and-agents-md`)。已经有你自己的 `CLAUDE.md` 时不导入,因为那里本来就不读 `AGENTS.md`。`unadopt` 会删除只剩这些行和托管块的 `CLAUDE.md`。带托管块的项目在**每次 SessionStart** 都会同步,出货模板变了会刷新,且不会再叠加本地副本或注入,从子目录启动的会话也一样。只有托管块的 `CLAUDE.md`(旧版本自动 adopt 写的)会在下一次从它所在目录启动的会话里补上同样的 `AGENTS.md` 导入,并给一次性提示。从子目录启动的会话看来,这个导入指向工作目录之外,Claude Code 会请你批准一次。插件创建过、又被你删掉(或被 `claude-mem-lite unadopt` 删掉)的本地托管块(`CLAUDE.local.md` 或 rules 文件)不会被写回,改为注入,直到运行 `claude-mem-lite adopt --enable`(在 `CLAUDE.local.md` 会让 `AGENTS.md` 失效的仓库里,还要设 `CLAUDE_MEM_RULES_STEERING=1` 才会再写文件)。关闭方式:项目级 `claude-mem-lite adopt --disable`(同时删除本地托管块;`CLAUDE.md` 托管块保留到 `unadopt`;重新启用用 `--enable`);全局 `export MEM_NO_AUTO_ADOPT=1`(已写入的托管块保留并继续加载,直到 `unadopt`);只冻结模板刷新用 `CLAUDE_MEM_NO_TEMPLATE_REFRESH=1`。`claude-mem-lite unadopt` 会移除 `CLAUDE.md` 托管块、详情文件,以及本地托管块(`CLAUDE.local.md` 或 `.claude/rules/claude-mem-lite.md`)和它的 exclude 条目。
160
160
  3. **配置钩子** -- 全部七个生命周期事件:`SessionStart`、`PreCompact`、`PreToolUse`、`PostToolUse`、`PostToolUseFailure`、`Stop`、`UserPromptSubmit`
161
161
  4. **创建数据目录** -- `~/.claude-mem-lite/`(隐藏目录),存放数据库与运行时文件
162
- 5. **自动迁移** -- 自动检测 `~/.claude-mem/`(原版 claude-mem)或 `~/claude-mem-lite/`(v0.5 前的非隐藏目录),将数据库和运行时文件迁移到 `~/.claude-mem-lite/`,原目录保持不变
162
+ 5. **自动迁移** -- `~/.claude-mem/claude-mem.db`(原版 claude-mem)以 `.legacy-backup-<时间>` 文件的形式移入 `~/.claude-mem-lite/`,并新建一个数据库。`~/claude-mem-lite/`(首个发布版 2.0.0 之前的非隐藏目录)里留下的数据库在第一次打开时移入数据目录,前提是数据目录里还没有 claude-mem-lite 的数据库、日志、快照或保留副本;两个目录里的其他文件都不移动、不删除
163
163
  6. **初始化数据库** -- SQLite WAL 模式,FTS5 索引在服务器首次启动时创建
164
164
 
165
165
  安装后重启 Claude Code 以激活。
@@ -169,20 +169,21 @@ node install.mjs install
169
169
  所有安装方式自动检测并从旧版本迁移:
170
170
 
171
171
  **从 claude-mem 原版(`~/.claude-mem/`):**
172
- - 复制 `claude-mem.db` → `~/.claude-mem-lite/claude-mem-lite.db`(重命名)
173
- - 复制 `runtime/` 目录
174
- - **原 `~/.claude-mem/` 保持不变**(不删除、不覆盖)
172
+ - `claude-mem.db` 连同 `-wal`/`-shm` 以 `.legacy-backup-<时间>` 的名字(`claude-mem-lite.db.legacy-backup-<时间>`、`claude-mem-lite.db-wal.legacy-backup-<时间>`)移入 `~/.claude-mem-lite/`,前提是 `~/.claude-mem-lite/claude-mem-lite.db` 还不存在
173
+ - 插件安装只把 `claude-mem.db` 以上面第一个名字复制进数据目录,`~/.claude-mem/` 原样不动;数据目录里既没有数据库、也没有这样的备份时才复制
174
+ - 新建一个数据库:原版 claude-mem 的 schema 没有升级路径
175
175
 
176
- **从 v0.5 前的非隐藏目录(`~/claude-mem-lite/`):**
177
- - 整个目录移动到 `~/.claude-mem-lite/`(隐藏目录)
176
+ **从首个发布版(2.0.0)之前的非隐藏目录(`~/claude-mem-lite/`):**
177
+ - 它的数据库(`claude-mem-lite.db` 或 `claude-mem.db`,连同 `-wal` 和 `-shm`)在第一次打开时移入数据目录,前提是数据目录里还没有 claude-mem-lite 的数据库、日志、快照或保留副本。其他文件都不移动,两个目录里都不删除任何东西
178
+ - 移动用的是改名:`CLAUDE_MEM_DIR` 在另一个文件系统上时会失败,之后每次打开都失败,直到手动把数据库连同 `-wal` 和 `-shm` 移过去
178
179
 
179
180
  **就地重命名:**
180
181
  - 已有的 `~/.claude-mem-lite/claude-mem.db` 会自动重命名为 `claude-mem-lite.db`
181
182
 
182
- 确认一切正常后手动删除旧目录:
183
+ 确认里面的东西都不再需要后,手动删除旧目录。`~/claude-mem-lite/` 里还在的数据库没有被迁移(见上文),下面的命令会删掉它:
183
184
  ```bash
184
185
  rm -rf ~/.claude-mem/ # 原版 claude-mem
185
- rm -rf ~/claude-mem-lite/ # v0.5 前的非隐藏目录(如未自动迁移)
186
+ rm -rf ~/claude-mem-lite/ # 首个发布版之前的非隐藏目录(不是本仓库的 git 克隆)
186
187
  ```
187
188
 
188
189
  ### 目录结构
@@ -839,7 +840,7 @@ claude-mem-lite/
839
840
  skill.md # MCP 技能定义(npx/git clone 模式)
840
841
  package.json # 依赖和元数据
841
842
  scripts/
842
- setup.sh # Setup 钩子:npm install + 迁移(隐藏目录 + 旧目录)
843
+ setup.sh # Setup 钩子:npm install + 迁移(原版 claude-mem 数据库、旧文件名)
843
844
  post-tool-use.sh # Bash 预过滤器:~5ms 跳过噪声,追踪 Read 路径
844
845
  user-prompt-search.js # UserPromptSubmit 钩子:用户提问时自动搜索记忆
845
846
  pre-tool-recall.js # PreToolUse 钩子:Edit/Write 前文件教训回忆
package/install.mjs CHANGED
@@ -530,18 +530,12 @@ export async function withSwapBarrier(fn) {
530
530
  }
531
531
  }
532
532
 
533
- // Creates the data dirs the swap barrier then writes its marker into. The legacy-dir migration
534
- // here runs only under CLAUDE_MEM_DIR relocation: in the default shape runLockedInstall's
535
- // install.lock has created ~/.claude-mem-lite before install() starts (pre-existing, D#238).
533
+ // Creates the data dirs the swap barrier then writes its marker into. A store left in the
534
+ // unhidden dir of versions before the first release (~/claude-mem-lite/) is ensureDb's to move
535
+ // (migrateUnhiddenStore). Install no longer renames that directory into the code dir (D#561): it
536
+ // did so only under CLAUDE_MEM_DIR relocation, where the code dir does not exist yet, and that
537
+ // directory can be a git clone of this repository made in ~.
536
538
  function prepareInstallDirs() {
537
- // Auto-migrate unhidden dir (~/claude-mem-lite/ → ~/.claude-mem-lite/)
538
- const oldUnhidden = join(homedir(), 'claude-mem-lite');
539
- if (!existsSync(DATA_DIR) && existsSync(oldUnhidden)) {
540
- log('Migrating ~/claude-mem-lite/ → ~/.claude-mem-lite/...');
541
- renameSync(oldUnhidden, DATA_DIR);
542
- ok('Directory migrated');
543
- }
544
-
545
539
  if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
546
540
  // Under relocation the DB/managed/runtime live here, not in the code dir — create it too.
547
541
  if (!existsSync(MEM_DATA_DIR)) mkdirSync(MEM_DATA_DIR, { recursive: true });
@@ -34,7 +34,7 @@ export function isMemHook(cfg, roots = null) {
34
34
 
35
35
  // The product name only as the DIRECTORY holding one of our entry points — every shape an
36
36
  // install has written: `<dir>/hook.mjs <event>` (legacy direct) and `<dir>/scripts/<x>.js|mjs|sh`,
37
- // under `.claude-mem-lite`, the pre-v0.5 `claude-mem-lite`, or a checkout named after it
37
+ // under `.claude-mem-lite`, the pre-2.0.0 unhidden `claude-mem-lite`, or a checkout named after it
38
38
  // (`claude-mem-lite-main` from a zip), with either separator. A bare substring also took
39
39
  // `claude-mem-lite export > ~/backups/…` and `~/tools/claude-mem-lite-prompt-audit.py`, which
40
40
  // install and uninstall then deleted. The directory name alone still took a user's own
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "6.30.5",
3
+ "version": "6.30.7",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "claude-mem-lite",
9
- "version": "6.30.5",
9
+ "version": "6.30.7",
10
10
  "os": [
11
11
  "darwin",
12
12
  "linux",
@@ -22,11 +22,11 @@
22
22
  },
23
23
  "devDependencies": {
24
24
  "@eslint/js": "^10.0.1",
25
- "@vitest/coverage-v8": "^5.0.3",
25
+ "@vitest/coverage-v8": "^5.0.5",
26
26
  "acorn": "8.19.0",
27
- "eslint": "^10.12.0",
27
+ "eslint": "^10.13.0",
28
28
  "fast-check": "^4.10.2",
29
- "knip": "^6.41.0",
29
+ "knip": "^6.42.1",
30
30
  "picomatch": "^4.0.4",
31
31
  "prettier": "3.9.10",
32
32
  "vitest": "^5.0.3"
@@ -146,18 +146,18 @@
146
146
  }
147
147
  },
148
148
  "node_modules/body-parser": {
149
- "version": "2.3.0",
150
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
151
- "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
149
+ "version": "2.4.0",
150
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.4.0.tgz",
151
+ "integrity": "sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==",
152
152
  "license": "MIT",
153
153
  "dependencies": {
154
154
  "bytes": "^3.1.2",
155
- "content-type": "^2.0.0",
155
+ "content-type": "^2.1.0",
156
156
  "debug": "^4.4.3",
157
157
  "http-errors": "^2.0.1",
158
- "iconv-lite": "^0.7.2",
158
+ "iconv-lite": "^0.7.3",
159
159
  "on-finished": "^2.4.1",
160
- "qs": "^6.15.2",
160
+ "qs": "^6.16.1",
161
161
  "raw-body": "^3.0.2",
162
162
  "type-is": "^2.1.0"
163
163
  },
@@ -170,9 +170,9 @@
170
170
  }
171
171
  },
172
172
  "node_modules/body-parser/node_modules/content-type": {
173
- "version": "2.0.0",
174
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
175
- "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
173
+ "version": "2.1.0",
174
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
175
+ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
176
176
  "license": "MIT",
177
177
  "engines": {
178
178
  "node": ">=18"
@@ -480,9 +480,9 @@
480
480
  "license": "MIT"
481
481
  },
482
482
  "node_modules/fast-uri": {
483
- "version": "3.1.8",
484
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
485
- "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
483
+ "version": "3.1.9",
484
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz",
485
+ "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==",
486
486
  "funding": [
487
487
  {
488
488
  "type": "github",
@@ -646,9 +646,9 @@
646
646
  }
647
647
  },
648
648
  "node_modules/iconv-lite": {
649
- "version": "0.7.2",
650
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
651
- "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
649
+ "version": "0.7.3",
650
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
651
+ "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
652
652
  "license": "MIT",
653
653
  "dependencies": {
654
654
  "safer-buffer": ">= 2.1.2 < 3.0.0"
@@ -894,9 +894,9 @@
894
894
  }
895
895
  },
896
896
  "node_modules/qs": {
897
- "version": "6.16.0",
898
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
899
- "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
897
+ "version": "6.16.1",
898
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.1.tgz",
899
+ "integrity": "sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==",
900
900
  "license": "BSD-3-Clause",
901
901
  "dependencies": {
902
902
  "es-define-property": "^1.0.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "6.30.5",
3
+ "version": "6.30.7",
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",
@@ -249,11 +249,11 @@
249
249
  },
250
250
  "devDependencies": {
251
251
  "@eslint/js": "^10.0.1",
252
- "@vitest/coverage-v8": "^5.0.3",
252
+ "@vitest/coverage-v8": "^5.0.5",
253
253
  "acorn": "8.19.0",
254
- "eslint": "^10.12.0",
254
+ "eslint": "^10.13.0",
255
255
  "fast-check": "^4.10.2",
256
- "knip": "^6.41.0",
256
+ "knip": "^6.42.1",
257
257
  "picomatch": "^4.0.4",
258
258
  "prettier": "3.9.10",
259
259
  "vitest": "^5.0.3"
package/schema.mjs CHANGED
@@ -1657,6 +1657,30 @@ function capBusyWaits(db) {
1657
1657
  };
1658
1658
  }
1659
1659
 
1660
+ /**
1661
+ * Move a store left in the unhidden data dir of versions before the first release (2.0.0),
1662
+ * ~/claude-mem-lite/, into DB_DIR. Only the store's own files move (the database, then its -wal
1663
+ * and -shm, under the same name; ensureDb then renames claude-mem.db), and only into a DB_DIR
1664
+ * holding no file of a store's family: a database, a log, a snapshot or a kept copy. install's
1665
+ * backup of an original claude-mem store (migrateLegacyClaudeMemData, `.legacy-backup-<ms>`) is
1666
+ * not one. Nothing is removed. It used to rename the whole directory over a DB_DIR it first
1667
+ * removed when DB_DIR held no `*.db` name (D#561): after the set-aside command that took the kept
1668
+ * file, DB_DIR is also the code dir of a direct install, and a git clone of this repository made
1669
+ * in ~ is at that path.
1670
+ */
1671
+ function migrateUnhiddenStore() {
1672
+ const oldDir = join(homedir(), 'claude-mem-lite');
1673
+ const name = ['claude-mem-lite.db', 'claude-mem.db'].find((n) => existsSync(join(oldDir, n)));
1674
+ if (!name) return;
1675
+ const storeFile = (f) =>
1676
+ (f.startsWith('claude-mem-lite.db') || f.startsWith('claude-mem.db')) && !/\.legacy-backup-\d+$/.test(f);
1677
+ if (existsSync(DB_DIR) && readdirSync(DB_DIR).some(storeFile)) return;
1678
+ mkdirSync(DB_DIR, { recursive: true, mode: 0o700 });
1679
+ for (const ext of ['', '-wal', '-shm']) {
1680
+ if (existsSync(join(oldDir, name + ext))) renameSync(join(oldDir, name + ext), join(DB_DIR, name + ext));
1681
+ }
1682
+ }
1683
+
1660
1684
  /**
1661
1685
  * Ensure DB directory, database file, and all tables exist.
1662
1686
  * Safe to call from any process (hook or server). Idempotent.
@@ -1671,19 +1695,7 @@ export function ensureDb() {
1671
1695
  code: 'CLAUDE_MEM_DATA_DIR_GONE',
1672
1696
  });
1673
1697
  }
1674
- // Auto-migrate unhidden dir (~/claude-mem-lite/ → ~/.claude-mem-lite/)
1675
- // Check DB_PATH (not DB_DIR) because hook-shared.mjs module-level init may create DB_DIR early
1676
- const oldUnhidden = join(homedir(), 'claude-mem-lite');
1677
- if (existsSync(oldUnhidden) && !existsSync(DB_PATH)) {
1678
- // Remove DB_DIR only if it has no user data (no .db files)
1679
- if (existsSync(DB_DIR)) {
1680
- try {
1681
- const hasDbFiles = readdirSync(DB_DIR).some((f) => f.endsWith('.db'));
1682
- if (!hasDbFiles) rmSync(DB_DIR, { recursive: true, force: true });
1683
- } catch {}
1684
- }
1685
- if (!existsSync(DB_DIR)) renameSync(oldUnhidden, DB_DIR);
1686
- }
1698
+ if (!existsSync(DB_PATH)) migrateUnhiddenStore();
1687
1699
 
1688
1700
  if (!existsSync(DB_DIR)) mkdirSync(DB_DIR, { recursive: true, mode: 0o700 });
1689
1701
 
package/scripts/setup.sh CHANGED
@@ -99,8 +99,6 @@ if [[ -n "${CLAUDE_MEM_DIR:-}" || -n "${CLAUDE_MEM_RUNTIME_DIR:-}" ]] && [[ -f "
99
99
  unset _resolved _db _rt
100
100
  fi
101
101
 
102
- OLD_UNHIDDEN_DIR="$HOME/claude-mem-lite"
103
-
104
102
  # Colors
105
103
  if [[ -t 2 ]]; then
106
104
  GREEN='\033[0;32m'; YELLOW='\033[0;33m'; BLUE='\033[0;34m'; RED='\033[0;31m'; NC='\033[0m'
@@ -120,17 +118,12 @@ log_warn() { echo -e "${YELLOW}⚠${NC} $*" >&2; }
120
118
  # day GitHub bumps the runner image. Keep SC2317 for anyone on an older shellcheck.
121
119
  log_err() { echo -e "${RED}✗${NC} $*" >&2; }
122
120
 
123
- # 1. Migrate unhidden dir (~/claude-mem-lite/ → ~/.claude-mem-lite/)
124
- # CODE_DIR, not DB_DIR, and deliberately: the pre-v0.5 unhidden directory held the
125
- # INSTALL — server.mjs, hook.mjs, package.json — and CODE_DIR is the one location that
126
- # must never follow the relocation env var. Moving it into a relocated DB_DIR would
127
- # strand every absolute path settings.json and the MCP registration baked.
128
- if [[ -d "$OLD_UNHIDDEN_DIR" && ! -d "$CODE_DIR" ]]; then
129
- mv "$OLD_UNHIDDEN_DIR" "$CODE_DIR"
130
- log_ok "Migrated ~/claude-mem-lite/ → ~/.claude-mem-lite/"
131
- fi
121
+ # 1. ~/claude-mem-lite/ (the unhidden dir of versions before the first release) is not moved
122
+ # here: the first open moves a store left there into DB_DIR, and only the store
123
+ # (schema.mjs migrateUnhiddenStore). This script moved the whole directory into a missing
124
+ # CODE_DIR, and a git clone of this repository made in ~ is at that path (D#568).
132
125
 
133
- # 2. Ensure both locations exist (runtime created after migration check)
126
+ # 2. Ensure both locations exist
134
127
  mkdir -p "$CODE_DIR"
135
128
  mkdir -p "$DB_DIR"
136
129
  if [[ "$DB_DIR" == "$CODE_DIR" ]]; then
@@ -150,8 +143,19 @@ fi
150
143
  # a relocation it never closed — nothing ever writes a database THERE — so this block
151
144
  # copied the legacy database again on every single SessionStart, without bound. Measured
152
145
  # 2026-09-14: control arm stable at 1 backup across three runs, relocated arm 1 → 3.
146
+ # A backup already in DB_DIR closes it as well: while every open fails before creating the
147
+ # database (D#569's EXDEV did), the first condition never closes (D#573). Only the suffix is a
148
+ # pattern, so a `[` or `*` in DB_DIR's own path is matched as itself.
153
149
  OLD_DIR="$HOME/.claude-mem"
154
- if [[ -f "$OLD_DIR/claude-mem.db" && ! -f "$DB_DIR/claude-mem-lite.db" && ! -f "$DB_DIR/claude-mem.db" ]]; then
150
+ has_legacy_backup() {
151
+ local f
152
+ for f in "$DB_DIR"/claude-mem-lite.db.legacy-backup-*; do
153
+ [[ -e "$f" ]] && return 0
154
+ done
155
+ return 1
156
+ }
157
+ if [[ -f "$OLD_DIR/claude-mem.db" && ! -f "$DB_DIR/claude-mem-lite.db" && ! -f "$DB_DIR/claude-mem.db" ]] &&
158
+ ! has_legacy_backup; then
155
159
  BACKUP="$DB_DIR/claude-mem-lite.db.legacy-backup-$(date +%s)"
156
160
  if cp "$OLD_DIR/claude-mem.db" "$BACKUP" 2>/dev/null; then
157
161
  log_info "Legacy ~/.claude-mem/ DB is schema-incompatible; backed up to $(basename "$BACKUP") (a fresh DB will be created). Old ~/.claude-mem/ preserved."
@@ -169,7 +173,7 @@ if [[ -f "$DB_DIR/claude-mem.db" && ! -f "$DB_DIR/claude-mem-lite.db" ]]; then
169
173
  log_ok "Database renamed: claude-mem.db → claude-mem-lite.db"
170
174
  fi
171
175
 
172
- # 5. Ensure runtime directories exist (after migration to not mask migration check).
176
+ # 5. Ensure runtime directories exist.
173
177
  # Both: RUNTIME_DIR carries the cross-component flag below, while CODE_DIR/runtime keeps
174
178
  # the two run-once install markers at the bottom of this file.
175
179
  mkdir -p "$RUNTIME_DIR"