claude-mem-lite 6.30.2 → 6.30.4

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.2",
12
+ "version": "6.30.4",
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.2",
3
+ "version": "6.30.4",
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/install.mjs CHANGED
@@ -478,16 +478,19 @@ export async function dbCheckRemedy(dbPath, err) {
478
478
  const remedy = dbUnusableRemedy(dbPath, err);
479
479
  if (remedy.kind === 'unknown') return remedy.note;
480
480
  if (remedy.kind === 'schema') return `SQLite cannot read the database's schema. ${remedy.note}`;
481
+ // Both commands start by deleting the -wal and -shm and then replace or move the database file,
482
+ // under any session still holding the store (D#534). The SessionStart notice says the same before
483
+ // its copy of the command.
481
484
  if (remedy.kind === 'set-aside') {
482
485
  return (
483
- `No backup snapshot exists beside the database. Set the broken file aside so a fresh ` +
484
- `store is created on the next session: ${remedy.command} ` +
486
+ `No non-empty backup snapshot exists beside the database. Once every Claude Code session is closed, ` +
487
+ `set the broken file aside so a fresh store is created on the next session: ${remedy.command} ` +
485
488
  `— memories in that file are not recoverable without a backup.`
486
489
  );
487
490
  }
488
491
  return (
489
- `Restore the newest of ${remedy.snapshotCount} backup snapshot(s): ${remedy.command} ` +
490
- `— move the broken file aside first if you want to keep it for inspection.`
492
+ `Restore the newest of ${remedy.snapshotCount} backup snapshot(s) once every Claude Code session ` +
493
+ `is closed: ${remedy.command} — move the broken file aside first if you want to keep it for inspection.`
491
494
  );
492
495
  }
493
496
 
package/lib/db-backup.mjs CHANGED
@@ -7,7 +7,7 @@
7
7
  //
8
8
  // MUST be called OUTSIDE any transaction — VACUUM cannot run inside one, which is
9
9
  // why the maintenance entry points snapshot before opening their db.transaction().
10
- import { readdirSync, unlinkSync, statSync } from 'fs';
10
+ import { existsSync, readdirSync, unlinkSync, statSync } from 'fs';
11
11
  import { dirname, basename, join } from 'path';
12
12
  import { debugLog } from '../utils.mjs';
13
13
 
@@ -149,7 +149,20 @@ export function snapshotDb(db, { tag = 'pre-maintain', retain = 3 } = {}) {
149
149
  const out = `${dbPath}.${tag}-${stamp}.bak`;
150
150
  // Path is internal (db.name from our own config), but escape single quotes
151
151
  // defensively since VACUUM INTO takes a string literal, not a bound param.
152
- db.exec(`VACUUM INTO '${out.replace(/'/g, "''")}'`);
152
+ const fresh = !existsSync(out);
153
+ try {
154
+ db.exec(`VACUUM INTO '${out.replace(/'/g, "''")}'`);
155
+ } catch (e) {
156
+ // A failed VACUUM INTO can leave its target behind: 0 bytes on a damaged store, newer than
157
+ // every real snapshot, so a restore command named it (D#551). Only a file it created goes.
158
+ if (fresh)
159
+ try {
160
+ unlinkSync(out);
161
+ } catch {
162
+ /* never created */
163
+ }
164
+ throw e;
165
+ }
153
166
  pruneSnapshots(dbPath, tag, retain);
154
167
  enforceBackupBudget(dbPath);
155
168
  return out;
@@ -171,46 +171,52 @@ export function dbUnusableRemedy(dbPath, err) {
171
171
  `fix that directory first, then look for ${basename(dbPath)}.*.bak beside the database.`,
172
172
  };
173
173
  }
174
- if (schema && snap.snapshots.length === 0) {
174
+ // A 0-byte `.bak` is what a failed snapshot of a damaged store left before 6.31.0 (D#551),
175
+ // usually the newest one; it is not a copy of anything.
176
+ const snapshots = snap.snapshots.filter((s) => s.size > 0);
177
+ const empty = snap.snapshots.length - snapshots.length;
178
+ const none =
179
+ empty > 0
180
+ ? `No backup snapshot beside the database holds any data (${empty} empty .bak file${empty === 1 ? '' : 's'} skipped).`
181
+ : 'No backup snapshot exists beside the database.';
182
+ if (schema && snapshots.length === 0) {
175
183
  return {
176
184
  kind: 'schema',
177
185
  command: '',
178
186
  snapshotCount: 0,
179
- note: 'No backup snapshot exists beside the database.',
187
+ note: none,
180
188
  };
181
189
  }
182
- if (snap.snapshots.length === 0) {
190
+ if (snapshots.length === 0) {
183
191
  return {
184
192
  kind: 'set-aside',
185
193
  command: `${clear} && mv ${shellWord(dbPath)} ${shellWord(`${dbPath}.corrupt`)}`,
186
194
  snapshotCount: 0,
187
195
  note:
188
- 'No backup snapshot exists beside the database. That command sets the broken file ' +
196
+ `${none} That command sets the broken file ` +
189
197
  'aside so a fresh store is created on the next session — memories in it are not ' +
190
198
  'recoverable without a backup.',
191
199
  };
192
200
  }
193
201
  // Newest by mtime, ties broken by name (which carries an ISO stamp) so the answer is total
194
202
  // rather than dependent on readdir order — the D#9 shape.
195
- const newest = snap.snapshots
196
- .slice()
197
- .sort((a, b) => b.mtimeMs - a.mtimeMs || (a.path < b.path ? 1 : -1))[0];
203
+ const newest = snapshots.slice().sort((a, b) => b.mtimeMs - a.mtimeMs || (a.path < b.path ? 1 : -1))[0];
198
204
  if (schema) {
199
205
  return {
200
206
  kind: 'schema',
201
207
  command: '',
202
- snapshotCount: snap.snapshots.length,
208
+ snapshotCount: snapshots.length,
203
209
  note:
204
- `${snap.snapshots.length} backup snapshot(s) exist beside it; the newest is ${newest.path}. ` +
210
+ `${snapshots.length} backup snapshot(s) exist beside it; the newest is ${newest.path}. ` +
205
211
  'Restoring one replaces the database file, so copy the broken file aside first.',
206
212
  };
207
213
  }
208
214
  return {
209
215
  kind: 'restore',
210
216
  command: `${clear} && cp ${shellWord(newest.path)} ${shellWord(dbPath)}`,
211
- snapshotCount: snap.snapshots.length,
217
+ snapshotCount: snapshots.length,
212
218
  note:
213
- `Restores the newest of ${snap.snapshots.length} backup snapshot(s). Move the broken ` +
219
+ `Restores the newest of ${snapshots.length} backup snapshot(s). Move the broken ` +
214
220
  'file aside first if you want to keep it for inspection.',
215
221
  };
216
222
  }
@@ -229,7 +235,12 @@ export function formatDbUnusableNotice({ dbPath, remedy }) {
229
235
  ? ` ${dbPath} exists but SQLite cannot read its schema.`
230
236
  : ` ${dbPath} exists but SQLite does not recognise it as a database.`,
231
237
  ];
232
- if (remedy.command) lines.push(` ${remedy.command}`);
238
+ if (remedy.command) {
239
+ // Before the command: it deletes the -wal and -shm, then replaces or moves the database file,
240
+ // under any session still holding the store (D#534). doctor says the same.
241
+ lines.push(' Once every Claude Code session is closed, this one included, run:');
242
+ lines.push(` ${remedy.command}`);
243
+ }
233
244
  if (remedy.note) lines.push(` ${remedy.note}`);
234
245
  lines.push(` Saves and recall are disabled. \`${CLI_INVOKE} doctor\` re-checks.`);
235
246
  return lines.join('\n');
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "6.30.2",
3
+ "version": "6.30.4",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "claude-mem-lite",
9
- "version": "6.30.2",
9
+ "version": "6.30.4",
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.30.2",
3
+ "version": "6.30.4",
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",