claude-mem-lite 2.32.6 → 2.32.8

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.
Files changed (73) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.mcp.json +0 -0
  4. package/LICENSE +0 -0
  5. package/README.md +0 -0
  6. package/README.zh-CN.md +0 -0
  7. package/adopt-cli.mjs +0 -0
  8. package/adopt-content.mjs +0 -0
  9. package/bash-utils.mjs +0 -0
  10. package/commands/adopt.md +0 -0
  11. package/commands/bug.md +0 -0
  12. package/commands/lesson.md +0 -0
  13. package/commands/mem.md +0 -0
  14. package/commands/memory.md +0 -0
  15. package/commands/tools.md +0 -0
  16. package/commands/unadopt.md +0 -0
  17. package/commands/update.md +0 -0
  18. package/format-utils.mjs +0 -0
  19. package/haiku-client.mjs +0 -0
  20. package/hash-utils.mjs +0 -0
  21. package/hook-context.mjs +0 -0
  22. package/hook-episode.mjs +0 -0
  23. package/hook-handoff.mjs +30 -18
  24. package/hook-llm.mjs +0 -0
  25. package/hook-memory.mjs +0 -0
  26. package/hook-optimize.mjs +0 -0
  27. package/hook-semaphore.mjs +0 -0
  28. package/hook-shared.mjs +1 -0
  29. package/hook-update.mjs +0 -0
  30. package/hook.mjs +6 -4
  31. package/hooks/hooks.json +0 -0
  32. package/install-metadata.mjs +0 -0
  33. package/install.mjs +0 -0
  34. package/lib/activity.mjs +0 -0
  35. package/lib/doctor-benchmark.mjs +0 -0
  36. package/lib/git-state.mjs +0 -0
  37. package/lib/plan-reader.mjs +0 -0
  38. package/lib/startup-dashboard.mjs +0 -0
  39. package/lib/task-reader.mjs +0 -0
  40. package/mem-cli.mjs +0 -0
  41. package/memdir.mjs +0 -0
  42. package/nlp.mjs +0 -0
  43. package/package.json +1 -1
  44. package/plugin-cache-guard.mjs +0 -0
  45. package/project-utils.mjs +0 -0
  46. package/registry/preinstalled.json +0 -0
  47. package/registry-enricher.mjs +0 -0
  48. package/registry-github.mjs +0 -0
  49. package/registry-importer.mjs +0 -0
  50. package/registry-indexer.mjs +0 -0
  51. package/registry-retriever.mjs +0 -0
  52. package/registry-scanner.mjs +0 -0
  53. package/registry.mjs +0 -0
  54. package/resource-discovery.mjs +0 -0
  55. package/schema.mjs +0 -0
  56. package/scoring-sql.mjs +0 -0
  57. package/scripts/launch.mjs +0 -0
  58. package/scripts/pre-skill-bridge.js +0 -0
  59. package/scripts/pre-tool-recall.js +0 -0
  60. package/scripts/prompt-search-utils.mjs +40 -2
  61. package/scripts/user-prompt-search.js +19 -1
  62. package/secret-scrub.mjs +0 -0
  63. package/server-internals.mjs +0 -0
  64. package/server.mjs +0 -0
  65. package/skill.md +0 -0
  66. package/skip-tools.mjs +0 -0
  67. package/source-files.mjs +0 -0
  68. package/stop-words.mjs +0 -0
  69. package/synonyms.mjs +0 -0
  70. package/tfidf.mjs +0 -0
  71. package/tier.mjs +0 -0
  72. package/tool-schemas.mjs +0 -0
  73. package/utils.mjs +0 -0
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "claude-mem-lite",
13
- "version": "2.32.6",
13
+ "version": "2.32.8",
14
14
  "source": "./",
15
15
  "description": "Lightweight persistent memory system for Claude Code — FTS5 search, episode batching, error-triggered recall"
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.32.6",
3
+ "version": "2.32.8",
4
4
  "description": "Lightweight persistent memory system for Claude Code — FTS5 search, episode batching, error-triggered recall",
5
5
  "author": {
6
6
  "name": "sdsrss"
package/.mcp.json CHANGED
File without changes
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
File without changes
package/README.zh-CN.md CHANGED
File without changes
package/adopt-cli.mjs CHANGED
File without changes
package/adopt-content.mjs CHANGED
File without changes
package/bash-utils.mjs CHANGED
File without changes
package/commands/adopt.md CHANGED
File without changes
package/commands/bug.md CHANGED
File without changes
File without changes
package/commands/mem.md CHANGED
File without changes
File without changes
package/commands/tools.md CHANGED
File without changes
File without changes
File without changes
package/format-utils.mjs CHANGED
File without changes
package/haiku-client.mjs CHANGED
File without changes
package/hash-utils.mjs CHANGED
File without changes
package/hook-context.mjs CHANGED
File without changes
package/hook-episode.mjs CHANGED
File without changes
package/hook-handoff.mjs CHANGED
@@ -4,7 +4,8 @@
4
4
  import { basename } from 'path';
5
5
  import { truncate, extractMatchKeywords, tokenizeHandoff, isSpecificTerm, LOW_SIGNAL_TITLE } from './utils.mjs';
6
6
  import {
7
- HANDOFF_EXPIRY_CLEAR, HANDOFF_EXPIRY_EXIT, HANDOFF_MATCH_THRESHOLD, CONTINUE_KEYWORDS,
7
+ HANDOFF_EXPIRY_CLEAR, HANDOFF_EXPIRY_EXIT, HANDOFF_ANCHOR_MAX_AGE,
8
+ HANDOFF_MATCH_THRESHOLD, CONTINUE_KEYWORDS,
8
9
  } from './hook-shared.mjs';
9
10
  // T10d: import the whole module (not a named export) so tests can spy on
10
11
  // gitStateModule.readGitState via vi.spyOn. Named-import bindings are
@@ -169,23 +170,23 @@ export function detectContinuationIntent(db, promptText, project, currentCcSessi
169
170
  if (!promptText || typeof promptText !== 'string') return false;
170
171
  if (promptText.trim().length < 2) return false;
171
172
 
172
- // T10d Stage -1: Git-commit anchor — if ANY handoff (any age) has a
173
- // git_sha_at_handoff matching current HEAD, the working tree hasn't moved
174
- // since that handoff, so assume continuation regardless of time / prompt.
173
+ // T10d Stage -1: Git-commit anchor — current HEAD == a stored
174
+ // git_sha_at_handoff working tree hasn't moved since the handoff.
175
175
  //
176
- // Trade-off: after weeks of no commits this fires aggressively. Users can
177
- // reset by making a commit or by typing a long unrelated prompt (this
178
- // anchor runs BEFORE the Stage 0 long-prompt guard, so that escape hatch
179
- // does not apply here). This is an MVP choice — see plan 10d concern.
176
+ // Age cap (HANDOFF_ANCHOR_MAX_AGE = 72h) prevents stale HEAD from
177
+ // auto-continuing weeks-old context. For older anchors, the rest of the
178
+ // pipeline (Stage 0/1/2) still evaluates normally.
180
179
  try {
181
180
  const currentSha = gitStateModule.readGitState({ cwd: process.cwd() }).headSha;
182
181
  if (currentSha) {
183
182
  const anchor = db.prepare(`
184
- SELECT 1 FROM session_handoffs
183
+ SELECT created_at_epoch FROM session_handoffs
185
184
  WHERE project = ? AND git_sha_at_handoff = ?
186
185
  ORDER BY created_at_epoch DESC LIMIT 1
187
186
  `).get(project, currentSha);
188
- if (anchor) return true;
187
+ if (anchor && (Date.now() - anchor.created_at_epoch <= HANDOFF_ANCHOR_MAX_AGE)) {
188
+ return true;
189
+ }
189
190
  }
190
191
  } catch { /* git/DB failure must not break the rest of the pipeline */ }
191
192
 
@@ -204,14 +205,25 @@ export function detectContinuationIntent(db, promptText, project, currentCcSessi
204
205
  `).get(project);
205
206
 
206
207
  if (clearHandoff && (Date.now() - clearHandoff.created_at_epoch <= HANDOFF_EXPIRY_CLEAR)) {
207
- // Short/ambiguous prompts: assume continuation (user may say "ok", "start", etc.)
208
- if (promptText.length < 40) return true;
209
- // Long prompts: check keyword overlap to confirm same-task intent
210
- if (!clearHandoff.match_keywords) return true; // no keywords stored, can't verify
211
- const clearPromptTokens = tokenizeHandoff(promptText);
212
- const clearHandoffTokens = new Set(tokenizeHandoff(clearHandoff.match_keywords));
213
- if (clearPromptTokens.some(t => clearHandoffTokens.has(t))) return true;
214
- // Long prompt with zero keyword overlap likely new task, fall through
208
+ const pTokens = tokenizeHandoff(promptText);
209
+ const hTokens = clearHandoff.match_keywords
210
+ ? new Set(tokenizeHandoff(clearHandoff.match_keywords))
211
+ : null;
212
+ const hasOverlap = hTokens && pTokens.some(t => hTokens.has(t));
213
+ if (promptText.length < 40) {
214
+ // Short prompts: session-scoped clear = same user/context, auto-continue.
215
+ // Unscoped (legacy / no session_id in hook input) requires an explicit
216
+ // continuation keyword or keyword overlap to avoid cross-session noise.
217
+ if (currentCcSessionId) return true;
218
+ if (CONTINUE_KEYWORDS.test(promptText)) return true;
219
+ if (hasOverlap) return true;
220
+ // Fall through
221
+ } else {
222
+ // Long prompts: check keyword overlap to confirm same-task intent
223
+ if (!clearHandoff.match_keywords) return true; // no keywords stored, can't verify
224
+ if (hasOverlap) return true;
225
+ // Long prompt with zero keyword overlap → likely new task, fall through
226
+ }
215
227
  }
216
228
 
217
229
  // Stage 1: Explicit keyword match — always works, even without handoff
package/hook-llm.mjs CHANGED
File without changes
package/hook-memory.mjs CHANGED
File without changes
package/hook-optimize.mjs CHANGED
File without changes
File without changes
package/hook-shared.mjs CHANGED
@@ -58,6 +58,7 @@ export function effectiveQuiet(cwd) {
58
58
  // Handoff system constants
59
59
  export const HANDOFF_EXPIRY_CLEAR = 6 * 3600000; // 6 hours (covers lunch/meeting breaks)
60
60
  export const HANDOFF_EXPIRY_EXIT = 7 * 24 * 60 * 60 * 1000; // 7 days
61
+ export const HANDOFF_ANCHOR_MAX_AGE = 72 * 3600000; // 72h cap on git_sha anchor — avoids stale-HEAD false positives
61
62
  export const HANDOFF_MATCH_THRESHOLD = 3; // min weighted score
62
63
  export const CONTINUE_KEYWORDS = /继续|接着|上次|之前的|前面的|刚才|\bcontinue\b|\bresume\b|\bwhere[\s-]+we[\s-]+left\b|\bpick[\s-]+up\b|\bcarry[\s-]+on\b/i;
63
64
 
package/hook-update.mjs CHANGED
File without changes
package/hook.mjs CHANGED
@@ -857,13 +857,15 @@ async function handleUserPrompt() {
857
857
  const injection = renderHandoffInjection(db, project, ccSessionId);
858
858
  if (injection) {
859
859
  process.stdout.write(injection + '\n');
860
- // Consume clear handoff after injection to prevent duplicate injection on prompts 2-3.
861
- // Scope the delete to THIS session's clear handoff do not clobber parallel sessions' rows.
860
+ // Consume handoff after injection to prevent re-injection on later prompts
861
+ // (within-session on prompts 2-3, or across new sessions for exit handoffs).
862
+ // clear: scoped to THIS session (parallel sessions keep their own rows).
863
+ // exit: unscoped — any exit handoff in this project is fair game once resumed.
862
864
  try {
863
865
  if (ccSessionId) {
864
- db.prepare("DELETE FROM session_handoffs WHERE project = ? AND type = 'clear' AND session_id = ?").run(project, ccSessionId);
866
+ db.prepare("DELETE FROM session_handoffs WHERE project = ? AND ((type = 'clear' AND session_id = ?) OR type = 'exit')").run(project, ccSessionId);
865
867
  } else {
866
- db.prepare("DELETE FROM session_handoffs WHERE project = ? AND type = 'clear'").run(project);
868
+ db.prepare("DELETE FROM session_handoffs WHERE project = ? AND type IN ('clear','exit')").run(project);
867
869
  }
868
870
  } catch {}
869
871
  }
package/hooks/hooks.json CHANGED
File without changes
File without changes
package/install.mjs CHANGED
File without changes
package/lib/activity.mjs CHANGED
File without changes
File without changes
package/lib/git-state.mjs CHANGED
File without changes
File without changes
File without changes
File without changes
package/mem-cli.mjs CHANGED
File without changes
package/memdir.mjs CHANGED
File without changes
package/nlp.mjs CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.32.6",
3
+ "version": "2.32.8",
4
4
  "description": "Lightweight persistent memory system for Claude Code",
5
5
  "type": "module",
6
6
  "engines": {
File without changes
package/project-utils.mjs CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/registry.mjs CHANGED
File without changes
File without changes
package/schema.mjs CHANGED
File without changes
package/scoring-sql.mjs CHANGED
File without changes
File without changes
File without changes
File without changes
@@ -49,8 +49,8 @@ export const INTENTS = [
49
49
  // CJK: 开发/编写/创建/构建/做一个/写一个 from real prompts
50
50
  { pattern: /implement|feature\b|add\s+(?:a\s+)?new|实现|添加|新功能|新增|开发|编写|创建|构建|做一个|加一个|写一个/i, type: null, limit: 3 },
51
51
  // Recall/history intent (catch-all temporal, lowest priority)
52
- // CJK: 刚才/历史/回顾 from real prompts
53
- { pattern: /before|previously|last time|remember|之前|上次|以前|记得|刚才|历史|回顾/i, type: null, limit: 5, useRecent: true },
52
+ // CJK: 刚才/历史/回顾 from real prompts; 碰到过|遇到过|见过|同样的问题 from spoken CN
53
+ { pattern: /before|previously|last time|remember|seen this|same\s+issue|之前|上次|以前|记得|刚才|历史|回顾|碰到过|遇到过|见过|同样的问题|类似的问题/i, type: null, limit: 5, useRecent: true },
54
54
  ];
55
55
 
56
56
  export function detectIntent(text) {
@@ -75,6 +75,44 @@ export function detectIntent(text) {
75
75
  return first;
76
76
  }
77
77
 
78
+ // ─── Error Signature Extraction ─────────────────────────────────────────────
79
+
80
+ /**
81
+ * Extract a canonical error signature from prompt text.
82
+ *
83
+ * Matches named exception/error classes like:
84
+ * - "TypeError: Cannot read properties of undefined (reading 'foo')"
85
+ * - "Error [ERR_MODULE_NOT_FOUND]: module X not found"
86
+ * - "AssertionError: expected 'a' to equal 'b'"
87
+ * - "ValueError: invalid literal for int()"
88
+ * - "thread 'main' panicked at ..." (Rust) → captured via Panic class
89
+ *
90
+ * Intentionally skips bare "Error: ..." without a typed class, and skips
91
+ * lowercase matches — those carry too little signal vs. the intent-based
92
+ * FTS path which already catches them.
93
+ *
94
+ * Returns { className, errorCode, message, signature } or null.
95
+ * `signature` is suitable for direct FTS5 search (sanitizeFtsQuery applies).
96
+ */
97
+ export function extractErrorSignature(text) {
98
+ if (!text || typeof text !== 'string') return null;
99
+ // Pass 1: typed class — "<CapCase>(Error|Exception|Panic)" with optional [ERR_CODE]
100
+ const TYPED_RE = /\b([A-Z][A-Za-z0-9]+(?:Error|Exception|Panic))(?:\s*\[([A-Z_][A-Z0-9_]*)\])?\s*:\s*([^\n]{3,200})/;
101
+ // Pass 2: bare "Error|Exception|Panic" followed by required [ERR_CODE] (Node idiom).
102
+ // Bare class without a code is skipped — too noisy; intent-based path catches those.
103
+ const BARE_CODED_RE = /\b(Error|Exception|Panic)\s*\[([A-Z_][A-Z0-9_]*)\]\s*:\s*([^\n]{3,200})/;
104
+ const m = text.match(TYPED_RE) || text.match(BARE_CODED_RE);
105
+ if (!m) return null;
106
+ const className = m[1];
107
+ const errorCode = m[2] || null;
108
+ const message = m[3].trim().replace(/\s+/g, ' ').replace(/[`'"]+$/, '');
109
+ const sigMsg = message.slice(0, 80);
110
+ const signature = errorCode
111
+ ? `${className} ${errorCode} ${sigMsg}`
112
+ : `${className} ${sigMsg}`;
113
+ return { className, errorCode, message, signature };
114
+ }
115
+
78
116
  // ─── Result Dedup ───────────────────────────────────────────────────────────
79
117
 
80
118
  export const MAX_SESSION_INJECTIONS = 15;
@@ -8,7 +8,7 @@ import { sanitizeFtsQuery, relaxFtsQueryToOr, truncate, typeIcon, inferProject,
8
8
  import { writeFileSync, readFileSync, existsSync, renameSync } from 'fs';
9
9
  import { join } from 'path';
10
10
  import Database from 'better-sqlite3';
11
- import { shouldSkip, detectIntent, shouldSkipByDedup, extractFiles, DEDUP_STALE_MS, matchRegistrySkillName } from './prompt-search-utils.mjs';
11
+ import { shouldSkip, detectIntent, shouldSkipByDedup, extractFiles, extractErrorSignature, DEDUP_STALE_MS, matchRegistrySkillName } from './prompt-search-utils.mjs';
12
12
 
13
13
  // ─── Constants ──────────────────────────────────────────────────────────────
14
14
 
@@ -267,6 +267,18 @@ async function main() {
267
267
  const intent = detectIntent(promptText);
268
268
  let rows = [];
269
269
 
270
+ // A (v2.32.8): precision pass for named errors. When the prompt contains
271
+ // a typed exception signature (TypeError/ValueError/ReferenceError/...),
272
+ // seed results with exact-match bugfix observations before the intent-
273
+ // based FTS flow runs. These hits are the most directly relevant and
274
+ // take priority slots in the merged output.
275
+ const errSig = extractErrorSignature(promptText);
276
+ const sigRows = errSig
277
+ ? searchByFts(db, errSig.signature, project, 2, 'bugfix').filter(r =>
278
+ typeof r.relevance === 'number' && Math.abs(r.relevance) >= BM25_MIN_SCORE
279
+ )
280
+ : [];
281
+
270
282
  if (intent?.useRecent) {
271
283
  // Recall intent: show recent observations
272
284
  rows = searchRecent(db, project, intent.limit);
@@ -302,6 +314,12 @@ async function main() {
302
314
  rows = rows.slice(0, MAX_RESULTS);
303
315
  }
304
316
 
317
+ // A (v2.32.8): prepend error-signature hits (higher precision), dedup, cap.
318
+ if (sigRows.length > 0) {
319
+ const sigIds = new Set(sigRows.map(r => r.id));
320
+ rows = [...sigRows, ...rows.filter(r => !sigIds.has(r.id))].slice(0, MAX_RESULTS);
321
+ }
322
+
305
323
  const candidateIds = rows.map(r => r.id);
306
324
  const dedupSkip = shouldSkipByDedup(candidateIds, INJECTED_IDS_FILE);
307
325
 
package/secret-scrub.mjs CHANGED
File without changes
File without changes
package/server.mjs CHANGED
File without changes
package/skill.md CHANGED
File without changes
package/skip-tools.mjs CHANGED
File without changes
package/source-files.mjs CHANGED
File without changes
package/stop-words.mjs CHANGED
File without changes
package/synonyms.mjs CHANGED
File without changes
package/tfidf.mjs CHANGED
File without changes
package/tier.mjs CHANGED
File without changes
package/tool-schemas.mjs CHANGED
File without changes
package/utils.mjs CHANGED
File without changes