claude-mem-lite 3.76.0 → 3.76.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.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "claude-mem-lite",
13
- "version": "3.76.0",
13
+ "version": "3.76.1",
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. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark)."
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "3.76.0",
3
+ "version": "3.76.1",
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. 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"
@@ -397,6 +397,20 @@ export function boostAccessed(db, { projectFilter, baseParams, opCap = OP_CAP })
397
397
  // background machinery must not quietly dispose of one.
398
398
  const NO_LESSON_SQL = "(lesson_learned IS NULL OR lesson_learned = '' OR lesson_learned = 'none')";
399
399
 
400
+ // The floor demotePinned writes, and therefore also the bound that decides whether a row
401
+ // still HAS anything to demote. One home: v3.76.0 shipped with the floor here and the
402
+ // forecast in maintenanceStats still hard-coded `importance > 1`, so a lesson row already
403
+ // at 2 was counted as pinned forever while every execute reported "Demoted 0" — the exact
404
+ // scan-forecast-vs-execute drift the decayAndMarkIdle comment below exists to prevent.
405
+ const PINNED_FLOOR_SQL = `(CASE WHEN ${NO_LESSON_SQL} THEN 1 ELSE 2 END)`;
406
+ // Shared by the op and its forecast. Callers add their own compressed_into/project filter.
407
+ // Module-private on purpose: nothing outside this file consumes it, and exporting by habit
408
+ // is how the knip baseline drifts (cf. the v3.70.0 DEFAULT_MARKETPLACE trio).
409
+ const PINNED_PREDICATE_SQL = `
410
+ COALESCE(injection_count, 0) >= ${PINNED_INJ_THRESHOLD}
411
+ AND COALESCE(cited_count, 0) = 0
412
+ AND COALESCE(importance, 1) > ${PINNED_FLOOR_SQL}`;
413
+
400
414
  /**
401
415
  * Repair the citation-decay blind spot: heavy-injection + zero-citation rows that
402
416
  * decay protects (injection_count>0) stay pinned at max importance forever. Floor
@@ -421,15 +435,12 @@ const NO_LESSON_SQL = "(lesson_learned IS NULL OR lesson_learned = '' OR lesson_
421
435
  * report phantom demotions on every run forever.
422
436
  */
423
437
  export function demotePinned(db, { projectFilter, baseParams, opCap = OP_CAP }) {
424
- const floor = `(CASE WHEN ${NO_LESSON_SQL} THEN 1 ELSE 2 END)`;
425
438
  return db.prepare(`
426
- UPDATE observations SET importance = ${floor}
439
+ UPDATE observations SET importance = ${PINNED_FLOOR_SQL}
427
440
  WHERE id IN (
428
441
  SELECT id FROM observations
429
442
  WHERE COALESCE(compressed_into, 0) = 0
430
- AND COALESCE(injection_count, 0) >= ${PINNED_INJ_THRESHOLD}
431
- AND COALESCE(cited_count, 0) = 0
432
- AND COALESCE(importance, 1) > ${floor}
443
+ AND ${PINNED_PREDICATE_SQL}
433
444
  ${projectFilter} LIMIT ${opCap}
434
445
  )
435
446
  `).run(...baseParams).changes;
@@ -603,9 +614,7 @@ export function maintenanceStats(db, { projectFilter, baseParams, staleAge }) {
603
614
  THEN 1 ELSE 0 END), 0) as broken,
604
615
  COALESCE(SUM(CASE WHEN COALESCE(access_count, 0) > 3 AND COALESCE(importance, 1) < 3
605
616
  THEN 1 ELSE 0 END), 0) as boostable,
606
- COALESCE(SUM(CASE WHEN COALESCE(injection_count, 0) >= ${PINNED_INJ_THRESHOLD}
607
- AND COALESCE(cited_count, 0) = 0 AND COALESCE(importance, 1) > 1
608
- THEN 1 ELSE 0 END), 0) as pinned
617
+ COALESCE(SUM(CASE WHEN ${PINNED_PREDICATE_SQL} THEN 1 ELSE 0 END), 0) as pinned
609
618
  FROM observations
610
619
  WHERE COALESCE(compressed_into, 0) = 0 ${projectFilter}
611
620
  `).get(staleAge, ...baseParams);
package/mem-cli.mjs CHANGED
@@ -2012,7 +2012,7 @@ function cmdMaintain(db, args) {
2012
2012
  out(` Stale (>30d, imp=1, no access, never injected): ${stats.stale}`);
2013
2013
  out(` Broken (no title/narrative): ${stats.broken}`);
2014
2014
  out(` Boostable (accessed>3, imp<3): ${stats.boostable}`);
2015
- out(` Pinned-but-uncited (inj>=${PINNED_INJ_THRESHOLD}, cited=0, imp>1): ${stats.pinned} — cleared by the default maintain set since v3.76.0 (opt out: CLAUDE_MEM_SKIP_DEMOTE_PINNED=1)`);
2015
+ out(` Pinned-but-uncited (inj>=${PINNED_INJ_THRESHOLD}, cited=0, above floor): ${stats.pinned} — floored by the default maintain set since v3.76.0, no lesson → 1, lesson → 2 (opt out: CLAUDE_MEM_SKIP_DEMOTE_PINNED=1)`);
2016
2016
  out(formatPendingPurgeLine(stats.pendingPurge));
2017
2017
  if (duplicates.length > 0) {
2018
2018
  const autoMergeable = duplicates.filter(d => parseFloat(d.similarity) >= AUTO_MERGE_THRESHOLD);
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "3.76.0",
3
+ "version": "3.76.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "claude-mem-lite",
9
- "version": "3.76.0",
9
+ "version": "3.76.1",
10
10
  "dependencies": {
11
11
  "@modelcontextprotocol/sdk": "^1.26.0",
12
12
  "better-sqlite3": "^12.6.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "3.76.0",
3
+ "version": "3.76.1",
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. 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",
package/server.mjs CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  recoverOrphanedChildren, recoverBuriedLessons, sweepDeferredWorkOrphans,
20
20
  purgeStale, purgeStalePreview, findDuplicates, maintenanceStats, rebuildVectors, vacuum,
21
21
  hardDeleteCandidateCount,
22
- OP_CAP, STALE_AGE_MS, resolveDefaultMaintainOps,
22
+ OP_CAP, STALE_AGE_MS, resolveDefaultMaintainOps, DEFAULT_MAINTAIN_OPS,
23
23
  } from './lib/maintain-core.mjs';
24
24
  import { snapshotDb } from './lib/db-backup.mjs';
25
25
  import { deleteObservations, previewDeleteRows } from './lib/delete-core.mjs';
@@ -1152,7 +1152,7 @@ server.registerTool(
1152
1152
  // T2-P1-A: reject explicit empty array (vs. omitted → defaults above). Empty-array
1153
1153
  // callers are almost always mistakes; silently running only FTS5 optimize hides the error.
1154
1154
  if (args.operations && args.operations.length === 0) {
1155
- return { content: [{ type: 'text', text: 'operations array is empty. Pass a non-empty list (e.g. ["cleanup","decay","boost"]) or omit operations to use the default set.' }], isError: true };
1155
+ return { content: [{ type: 'text', text: `operations array is empty. Pass a non-empty list (e.g. ${JSON.stringify(DEFAULT_MAINTAIN_OPS)}) or omit operations to use the default set.` }], isError: true };
1156
1156
  }
1157
1157
  const results = [];
1158
1158
  const staleAge = Date.now() - STALE_AGE_MS;