claude-mem-lite 2.86.0 → 2.87.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.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "claude-mem-lite",
13
- "version": "2.86.0",
13
+ "version": "2.87.0",
14
14
  "source": "./",
15
15
  "description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. Alternative to claude-mem with 600x lower cost."
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.86.0",
3
+ "version": "2.87.0",
4
4
  "description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. Alternative to claude-mem with 600x lower cost.",
5
5
  "author": {
6
6
  "name": "sdsrss"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.86.0",
3
+ "version": "2.87.0",
4
4
  "description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. Alternative to claude-mem with 600x lower cost.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",
package/schema.mjs CHANGED
@@ -49,7 +49,12 @@ export const REGISTRY_DB_PATH = join(DB_DIR, 'resource-registry.db');
49
49
  // cited_count, last_decided_session_id. Stop hook resolves injected obs as
50
50
  // cited|uncited; 3 consecutive uncited → importance -1 (floor 0); 1 cited → +1
51
51
  // (cap 3). last_decided_session_id makes Stop idempotent across multi-fire.
52
- export const CURRENT_SCHEMA_VERSION = 34;
52
+ // v35 (v2.87.0): no DDL — version bumped only to force one full migration pass on
53
+ // existing DBs, which runs the one-shot observation_files orphan cleanup (and
54
+ // re-runs the v28 observation_vectors cleanup) to clear the backlog leaked while
55
+ // the warm-start fast-path left foreign_keys OFF. LATEST_MIGRATION_COLUMN is
56
+ // unchanged (no new column) — decay_seen_count still exists at v35.
57
+ export const CURRENT_SCHEMA_VERSION = 35;
53
58
 
54
59
  // Sentinel column for the LATEST migration set. The fast-path uses this to
55
60
  // self-heal half-migrated DBs — schema_version bumped but column ALTERs rolled
@@ -212,7 +217,16 @@ export function initSchema(db) {
212
217
  // Self-heal: version-row says CURRENT but latest migration column may be
213
218
  // absent (rolled back / never applied). Fall through to migration apply
214
219
  // when the sentinel is missing — duplicates are caught in the loop.
215
- if (row.version === CURRENT_SCHEMA_VERSION && hasLatestMigrationColumn(db)) return db;
220
+ if (row.version === CURRENT_SCHEMA_VERSION && hasLatestMigrationColumn(db)) {
221
+ // Warm-start post-condition: ensureDb() opened this handle with
222
+ // foreign_keys=OFF (early migrations require cascade disabled). The full
223
+ // migration path re-enables it at the end; this fast-path return must
224
+ // match that post-condition, else every DELETE on the returned handle
225
+ // skips ON DELETE CASCADE and silently orphans junction rows. Safe here:
226
+ // no transaction is open yet (BEGIN IMMEDIATE is below).
227
+ db.pragma('foreign_keys = ON');
228
+ return db;
229
+ }
216
230
  if (row.version > CURRENT_SCHEMA_VERSION) {
217
231
  throw new Error(
218
232
  `DB schema is v${row.version} but this claude-mem-lite binary supports up to v${CURRENT_SCHEMA_VERSION}. ` +
@@ -237,6 +251,10 @@ export function initSchema(db) {
237
251
  const underlock = db.prepare('SELECT version FROM schema_version LIMIT 1').get();
238
252
  if (underlock && underlock.version === CURRENT_SCHEMA_VERSION && hasLatestMigrationColumn(db)) {
239
253
  db.exec('COMMIT');
254
+ // COMMIT closed the transaction, so this PRAGMA takes effect (no-op inside a txn).
255
+ // Same FK post-condition as the fast-path above: a peer completed init while we
256
+ // were blocked, so we skip migrations and must still restore cascade enforcement.
257
+ db.pragma('foreign_keys = ON');
240
258
  return db;
241
259
  }
242
260
  } catch { /* table absent — proceed */ }
@@ -472,6 +490,19 @@ export function initSchema(db) {
472
490
  }
473
491
  } catch { /* non-critical — migration can retry on next open */ }
474
492
 
493
+ // v35 (v2.87.0) P1: one-shot cleanup of orphaned observation_files. Same root
494
+ // cause as the v28 observation_vectors cleanup below — until the warm-start
495
+ // fast-path FK fix (initSchema early returns now restore foreign_keys=ON),
496
+ // ensureDb() handles ran with FK OFF, so ON DELETE CASCADE never fired and
497
+ // junction rows leaked (live DB: 6440/9569 = 67% orphans). Idempotent (NOT IN
498
+ // is empty on a clean DB); runs once per version bump via the fast-path gate.
499
+ try {
500
+ db.prepare(`
501
+ DELETE FROM observation_files
502
+ WHERE obs_id NOT IN (SELECT id FROM observations)
503
+ `).run();
504
+ } catch { /* non-critical — table-missing path handled by earlier CREATE */ }
505
+
475
506
  // Observation vectors table for TF-IDF vector search
476
507
  db.exec(`
477
508
  CREATE TABLE IF NOT EXISTS observation_vectors (