claude-mem-lite 2.2.2 → 2.2.3

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/dispatch.mjs CHANGED
@@ -693,7 +693,7 @@ function reRankByKeywords(results, rawKeywords) {
693
693
  * @returns {object[]} Filtered results with decayed scores
694
694
  */
695
695
  function applyAdoptionDecay(results, db) {
696
- return results.map(r => {
696
+ const decayed = results.map(r => {
697
697
  const recs = r.recommend_count || 0;
698
698
  const adopts = r.adopt_count || 0;
699
699
  if (recs < 10) return r; // Cold start protection
@@ -721,6 +721,12 @@ function applyAdoptionDecay(results, db) {
721
721
  }
722
722
  return r;
723
723
  }).filter(Boolean);
724
+ // Re-sort after decay: decayed zombies must drop in ranking.
725
+ // BM25 scores are negative (more negative = better match), sort ascending.
726
+ if (decayed.some(r => r._decayed)) {
727
+ decayed.sort((a, b) => (a.composite_score ?? a.relevance) - (b.composite_score ?? b.relevance));
728
+ }
729
+ return decayed;
724
730
  }
725
731
 
726
732
  /**
@@ -763,6 +769,24 @@ function passesConfidenceGate(results, signals) {
763
769
  });
764
770
  }
765
771
 
772
+ // ─── Shared Post-Processing Pipeline ────────────────────────────────────────
773
+
774
+ /**
775
+ * Standard post-processing pipeline for dispatch results.
776
+ * Applies keyword re-ranking, adoption decay, confidence gating, and limit.
777
+ * @param {object[]} results FTS5 results
778
+ * @param {object} signals Context signals
779
+ * @param {object} db Registry database
780
+ * @param {number} [limit=3] Maximum results to return
781
+ * @returns {object[]} Post-processed results
782
+ */
783
+ function postProcessResults(results, signals, db, limit = 3) {
784
+ results = reRankByKeywords(results, signals.rawKeywords);
785
+ results = applyAdoptionDecay(results, db);
786
+ results = passesConfidenceGate(results, signals);
787
+ return results.slice(0, limit);
788
+ }
789
+
766
790
  // ─── Main Dispatch Functions ─────────────────────────────────────────────────
767
791
 
768
792
  /**
@@ -807,10 +831,7 @@ export async function dispatchOnSessionStart(db, userPrompt, sessionId, { hasHan
807
831
  }
808
832
  }
809
833
 
810
- results = reRankByKeywords(results, signals.rawKeywords);
811
- results = applyAdoptionDecay(results, db);
812
- results = passesConfidenceGate(results, signals);
813
- results = results.slice(0, 3);
834
+ results = postProcessResults(results, signals, db);
814
835
 
815
836
  let tier = 2;
816
837
 
@@ -827,10 +848,7 @@ export async function dispatchOnSessionStart(db, userPrompt, sessionId, { hasHan
827
848
  projectDomains,
828
849
  });
829
850
  if (haikuResults.length > 0) {
830
- // Apply same post-processing as Tier2 to prevent zombie/low-confidence bypass
831
- haikuResults = reRankByKeywords(haikuResults, signals.rawKeywords);
832
- haikuResults = applyAdoptionDecay(haikuResults, db);
833
- haikuResults = passesConfidenceGate(haikuResults, signals);
851
+ haikuResults = postProcessResults(haikuResults, signals, db);
834
852
  if (haikuResults.length > 0) results = haikuResults;
835
853
  }
836
854
  }
@@ -935,13 +953,7 @@ export async function dispatchOnUserPrompt(db, userPrompt, sessionId, { sessionE
935
953
  }
936
954
  }
937
955
 
938
- // Re-rank: when rawKeywords are present, prefer resources whose intent_tags
939
- // match those keywords. "帮我做一下SEO审查" → rawKeywords=["seo"] → SEO audit
940
- // resources should rank above generic code-review resources.
941
- results = reRankByKeywords(results, signals.rawKeywords);
942
- results = applyAdoptionDecay(results, db);
943
- results = passesConfidenceGate(results, signals);
944
- results = results.slice(0, 3);
956
+ results = postProcessResults(results, signals, db);
945
957
 
946
958
  if (results.length === 0) return null;
947
959
 
@@ -1008,8 +1020,7 @@ export async function dispatchOnPreToolUse(db, event, sessionCtx = {}) {
1008
1020
 
1009
1021
  // Tier 2: FTS5 retrieval
1010
1022
  let results = retrieveResources(db, query, { limit: 3, projectDomains });
1011
- results = applyAdoptionDecay(results, db);
1012
- results = passesConfidenceGate(results, signals);
1023
+ results = postProcessResults(results, signals, db);
1013
1024
  if (results.length === 0) return null;
1014
1025
 
1015
1026
  const tier = 2; // Tier 3 disabled for PreToolUse — 2s hook timeout insufficient
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.2.2",
3
+ "version": "2.2.3",
4
4
  "description": "Lightweight persistent memory system for Claude Code",
5
5
  "type": "module",
6
6
  "engines": {
@@ -196,8 +196,9 @@ export function buildEnhancedQuery(signals) {
196
196
  // would dilute BM25 precision. Literal matching is sufficient — "seo" matches "seo"
197
197
  // directly across name, intent_tags, capability_summary, trigger_patterns.
198
198
  if (signals.rawKeywords?.length > 0) {
199
+ const isSafe = t => /^[a-zA-Z0-9]+$/.test(t) || /[\u4e00-\u9fff\u3400-\u4dbf]/.test(t);
199
200
  for (const kw of signals.rawKeywords) {
200
- const safeKw = expandToken(kw);
201
+ const safeKw = isSafe(kw) ? kw : `"${kw.replace(/"/g, '""')}"`;
201
202
  parts.push(`intent_tags:${safeKw}`);
202
203
  parts.push(safeKw);
203
204
  }
package/registry.mjs CHANGED
@@ -104,7 +104,7 @@ const INVOCATIONS_SCHEMA = `
104
104
  tier INTEGER CHECK(tier IN (1,2,3)),
105
105
  recommended INTEGER DEFAULT 1,
106
106
  adopted INTEGER DEFAULT 0,
107
- outcome TEXT CHECK(outcome IN ('success','partial','failure','skipped','ignored',NULL)),
107
+ outcome TEXT CHECK(outcome IN ('success','partial','failure','skipped','ignored') OR outcome IS NULL),
108
108
  score REAL,
109
109
  created_at TEXT DEFAULT (datetime('now'))
110
110
  );
@@ -161,7 +161,7 @@ export function ensureRegistryDb(dbPath) {
161
161
  if (!cols.some(c => c.name === 'invocation_name')) {
162
162
  db.exec("ALTER TABLE resources ADD COLUMN invocation_name TEXT DEFAULT ''");
163
163
  }
164
- } catch {}
164
+ } catch (e) { debugCatch(e, 'invocation_name-migration'); }
165
165
 
166
166
  // FTS5 + triggers: only create if not exists
167
167
  const hasFts = db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='resources_fts'`).get();
@@ -215,6 +215,10 @@ export function ensureRegistryDb(dbPath) {
215
215
  return db;
216
216
  }
217
217
 
218
+ // ─── Exported Schema (for test-helpers.mjs) ─────────────────────────────────
219
+
220
+ export { RESOURCES_SCHEMA, FTS5_SCHEMA, TRIGGERS_SCHEMA, INVOCATIONS_SCHEMA, PREINSTALLED_SCHEMA };
221
+
218
222
  // ─── Resource CRUD ───────────────────────────────────────────────────────────
219
223
 
220
224
  const UPSERT_SQL = `
package/server.mjs CHANGED
@@ -1001,7 +1001,9 @@ const idleTimer = setInterval(() => {
1001
1001
  const thirtyDaysAgo = Date.now() - 30 * 86400000;
1002
1002
 
1003
1003
  db.transaction(() => {
1004
- // Delete old low-quality observations (importance<=1, never accessed, 30+ days)
1004
+ // Delete old low-quality observations (importance<=1, never accessed, 30+ days).
1005
+ // NOTE: no project filter — MCP server is global, operates across all projects.
1006
+ // This is intentionally broader than hook.mjs auto-compress (which scopes to current project).
1005
1007
  const deleted = db.prepare(`
1006
1008
  DELETE FROM observations
1007
1009
  WHERE importance <= 1 AND COALESCE(access_count, 0) = 0