claude-mem-lite 2.12.2 → 2.13.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": "2.12.2",
13
+ "version": "2.13.1",
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.12.2",
3
+ "version": "2.13.1",
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/cli.mjs CHANGED
@@ -1,4 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { main } from './install.mjs';
2
+ const CLI_COMMANDS = new Set(['search', 'recent', 'recall', 'get', 'timeline', 'save', 'stats', 'context', 'help']);
3
3
 
4
- await main(process.argv.slice(2));
4
+ const cmd = process.argv[2];
5
+
6
+ if (CLI_COMMANDS.has(cmd)) {
7
+ const { run } = await import('./mem-cli.mjs');
8
+ await run(process.argv.slice(2));
9
+ } else {
10
+ const { main } = await import('./install.mjs');
11
+ await main(process.argv.slice(2));
12
+ }
package/commands/mem.md CHANGED
@@ -2,39 +2,36 @@
2
2
  description: Search and manage project memory (observations, sessions, prompts)
3
3
  ---
4
4
 
5
- # Memory Skill
5
+ # Memory
6
6
 
7
7
  Search and browse your project memory efficiently.
8
8
 
9
- ## Commands
9
+ ## Quick Commands
10
10
 
11
- - `/mem search <query>` — FTS5 full-text search across all memories
12
- - `/mem recent [n]` — Show recent N observations (default 10)
11
+ - `/mem search <query>` — Search all memories (FTS5 full-text search)
12
+ - `/mem recent [n]` — Show recent N observations (default 5)
13
+ - `/mem recall <file>` — History for a file before editing
14
+ - `/mem timeline <id>` — Browse timeline around an observation
13
15
  - `/mem save <text>` — Save a manual memory/note
14
16
  - `/mem stats` — Show memory statistics
15
- - `/mem timeline <query>` — Browse timeline around a matching observation
16
17
  - `/mem cleanup` — Scan and interactively purge stale data
17
18
  - `/mem cleanup [N]d` — Purge stale data older than N days (e.g. `cleanup 60d`)
18
19
  - `/mem cleanup keep [N]d` — Purge stale data but retain last N days (e.g. `cleanup keep 14d`)
19
20
 
20
- ## Efficient Search Workflow (3 steps, saves 10x tokens)
21
-
22
- 1. **Search** → `mem_search(query="...")` → get compact ID index
23
- 2. **Browse** → `mem_timeline(anchor=ID)` → see surrounding context
24
- 3. **Detail** → `mem_get(ids=[...])` → get full content for specific IDs
25
-
26
21
  ## Instructions
27
22
 
28
23
  When the user invokes `/mem`, parse their intent:
29
24
 
30
- - `/mem search <query>` → call `mem_search` with the query
31
- - `/mem recent` or `/mem recent 20` → call `mem_search` with no query, limit=N
32
- - `/mem save <text>` → call `mem_save` with the text as content
33
- - `/mem stats`call `mem_stats`
34
- - `/mem timeline <query>` → call `mem_timeline` with the query
25
+ - `/mem search <query>` → run `claude-mem-lite search <query>` via Bash
26
+ - `/mem recent` or `/mem recent 20` → run `claude-mem-lite recent [N]` via Bash
27
+ - `/mem recall <file>` → run `claude-mem-lite recall <file>` via Bash
28
+ - `/mem timeline <id>` run `claude-mem-lite timeline --anchor <id>` via Bash
29
+ - `/mem save <text>` → call `mem_save` MCP tool with the text as content
30
+ - `/mem stats` → run `claude-mem-lite stats` via Bash
31
+ - `/mem get <ids>` → run `claude-mem-lite get <ids>` via Bash
35
32
  - `/mem cleanup` → run `mem_maintain(action="scan")`, report pending purge count and stale items to user, ask for confirmation, then run `mem_maintain(action="execute", operations=["purge_stale"])` if confirmed
36
33
  - `/mem cleanup Nd` (e.g. `60d`) → same as above but use `retain_days=N` to only purge items older than N days
37
34
  - `/mem cleanup keep Nd` (e.g. `keep 14d`) → same as above with `retain_days=N`
38
- - `/mem <query>` (no subcommand) → treat as search, call `mem_search`
35
+ - `/mem <query>` (no subcommand) → treat as search, run `claude-mem-lite search <query>` via Bash
39
36
 
40
- Always use the compact index from mem_search first, then mem_get for details only when needed. This minimizes token usage.
37
+ Use Bash commands first. For detailed data, use `claude-mem-lite get <id>` via Bash.
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Recall past observations for a file before editing
3
+ argument-hint: <file_path>
4
+ ---
5
+
6
+ ## File Memory
7
+ !`claude-mem-lite recall $ARGUMENTS 2>/dev/null || echo "No history found"`
8
+
9
+ Consider these past observations before making changes.
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Show recent memory observations
3
+ argument-hint: [count]
4
+ ---
5
+
6
+ ## Recent Observations
7
+ !`claude-mem-lite recent $ARGUMENTS 2>/dev/null || echo "No observations"`
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Search memory for past bugfixes, decisions, discoveries
3
+ argument-hint: <query>
4
+ ---
5
+
6
+ ## Memory Search
7
+ !`claude-mem-lite search $ARGUMENTS 2>/dev/null || echo "No results found"`
8
+
9
+ Use `claude-mem-lite get <id>` via Bash for full details on any result.
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Browse memory timeline around an observation
3
+ argument-hint: <observation_id>
4
+ ---
5
+
6
+ ## Timeline
7
+ !`claude-mem-lite timeline --anchor $ARGUMENTS 2>/dev/null || echo "Not found"`
package/hook-shared.mjs CHANGED
@@ -4,10 +4,9 @@
4
4
  import { execFileSync, spawn } from 'child_process';
5
5
  import { randomUUID } from 'crypto';
6
6
  import { join } from 'path';
7
- import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync, renameSync, unlinkSync } from 'fs';
7
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from 'fs';
8
8
  import { inferProject, debugCatch } from './utils.mjs';
9
- import { ensureDb, DB_DIR, REGISTRY_DB_PATH } from './schema.mjs';
10
- import { ensureRegistryDb } from './registry.mjs';
9
+ import { ensureDb, DB_DIR } from './schema.mjs';
11
10
  import { getClaudePath as getClaudePathShared, resolveModel as resolveModelShared } from './haiku-client.mjs';
12
11
 
13
12
  // ─── Constants ────────────────────────────────────────────────────────────────
@@ -24,7 +23,6 @@ export const STALE_LOCK_MS = 30000; // 30s
24
23
  export const DEDUP_WINDOW_MS = 5 * 60 * 1000; // 5 min (title dedup)
25
24
  export const RELATED_OBS_WINDOW_MS = 7 * 86400000; // 7 days
26
25
  export const FALLBACK_OBS_WINDOW_MS = RELATED_OBS_WINDOW_MS; // same window
27
- export const RESOURCE_RESCAN_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
28
26
 
29
27
  // Handoff system constants
30
28
  export const HANDOFF_EXPIRY_CLEAR = 6 * 3600000; // 6 hours (covers lunch/meeting breaks)
@@ -69,20 +67,6 @@ export function openDb() {
69
67
  }
70
68
  }
71
69
 
72
- // ─── Registry Database (dispatch system) ─────────────────────────────────────
73
- let _registryDb = null;
74
-
75
- export function getRegistryDb() {
76
- if (_registryDb) return _registryDb;
77
- try { _registryDb = ensureRegistryDb(REGISTRY_DB_PATH); } catch (e) { debugCatch(e, 'getRegistryDb'); }
78
- return _registryDb;
79
- }
80
-
81
- export function closeRegistryDb() {
82
- if (_registryDb) try { _registryDb.close(); } catch {}
83
- _registryDb = null;
84
- }
85
-
86
70
  // ─── LLM via claude CLI ─────────────────────────────────────────────────────
87
71
 
88
72
  export function callLLM(prompt, timeoutMs = 15000) {
@@ -127,75 +111,6 @@ export function spawnBackground(bgEvent, ...extraArgs) {
127
111
 
128
112
  export function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
129
113
 
130
- // ─── Injection Budget (per hook invocation, in-memory) ───────────────────────
131
- // Limits context injections within a single hook process to prevent context bloat.
132
- // Note: each hook event runs in a separate process, so this is per-invocation,
133
- // not per-session. Session-level dedup is handled by cooldown/sessionId checks.
134
-
135
- export const MAX_INJECTIONS_PER_SESSION = 3;
136
- let _injectionCount = 0;
137
-
138
- export function getInjectionCount() { return _injectionCount; }
139
- export function incrementInjection() { _injectionCount++; }
140
- export function resetInjectionBudget() { _injectionCount = 0; }
141
- export function hasInjectionBudget() { return _injectionCount < MAX_INJECTIONS_PER_SESSION; }
142
-
143
- // ─── Previous Session Context (for user-prompt dispatch enrichment) ──────────
144
- // Session-start caches next_steps; first user-prompt reads+clears for richer dispatch.
145
-
146
- // ─── Tool Event Tracking (for dispatch feedback) ────────────────────────────
147
- // PostToolUse appends feedback-relevant tool events (Skill, Task, Edit, Write, Bash errors).
148
- // Stop handler reads them and passes to collectFeedback for adoption/outcome detection.
149
-
150
- export function toolEventsFile() {
151
- return join(RUNTIME_DIR, `tool-events-${inferProject()}.jsonl`);
152
- }
153
-
154
- /**
155
- * Append a tool event for feedback tracking.
156
- * Only call for feedback-relevant events (Skill, Task, Edit, Write, Bash).
157
- * @param {object} event { tool_name, tool_input, tool_response }
158
- */
159
- export function appendToolEvent(event) {
160
- try {
161
- appendFileSync(toolEventsFile(), JSON.stringify(event) + '\n');
162
- } catch {}
163
- }
164
-
165
- /**
166
- * Read all tracked tool events and remove the file.
167
- * Uses rename→read→delete for atomicity.
168
- * @returns {object[]} Array of tool event objects
169
- */
170
- export function readAndClearToolEvents() {
171
- const file = toolEventsFile();
172
- const claimFile = file + `.claim-${process.pid}-${Date.now()}`;
173
- try {
174
- renameSync(file, claimFile);
175
- const raw = readFileSync(claimFile, 'utf8');
176
- try { unlinkSync(claimFile); } catch {}
177
- return raw.trim().split('\n').filter(Boolean).map(line => {
178
- try { return JSON.parse(line); } catch { return null; }
179
- }).filter(Boolean);
180
- } catch {
181
- return [];
182
- }
183
- }
184
-
185
- /**
186
- * Read tracked tool events WITHOUT clearing the file.
187
- * Used by dispatch to detect active suites mid-session.
188
- * @returns {object[]} Array of tool event objects
189
- */
190
- export function peekToolEvents() {
191
- try {
192
- const raw = readFileSync(toolEventsFile(), 'utf8');
193
- return raw.trim().split('\n').filter(Boolean).map(line => {
194
- try { return JSON.parse(line); } catch { return null; }
195
- }).filter(Boolean);
196
- } catch { return []; }
197
- }
198
-
199
114
  /**
200
115
  * Extract partial response from CLI error output (timeout/error recovery).
201
116
  * @param {Error} error The caught error from execFileSync
package/hook-update.mjs CHANGED
@@ -194,13 +194,12 @@ export function getCurrentVersion() {
194
194
  // ── Source files to copy (must match install.mjs SOURCE_FILES) ──
195
195
  const SOURCE_FILES = [
196
196
  'server.mjs', 'server-internals.mjs', 'tool-schemas.mjs',
197
- 'hook.mjs', 'hook-shared.mjs', 'hook-llm.mjs', 'hook-memory.mjs',
197
+ 'hook.mjs', 'hook-shared.mjs', 'hook-llm.mjs', 'hook-memory.mjs', 'skip-tools.mjs',
198
198
  'hook-semaphore.mjs', 'hook-episode.mjs', 'hook-context.mjs', 'hook-handoff.mjs', 'hook-update.mjs',
199
199
  'haiku-client.mjs', 'utils.mjs', 'schema.mjs', 'package.json', 'package-lock.json', 'skill.md',
200
200
  'registry.mjs', 'registry-scanner.mjs', 'registry-indexer.mjs',
201
201
  'registry-retriever.mjs', 'resource-discovery.mjs',
202
- 'dispatch.mjs', 'dispatch-inject.mjs', 'dispatch-feedback.mjs', 'dispatch-patterns.mjs', 'dispatch-workflow.mjs',
203
- 'install.mjs', 'install-metadata.mjs',
202
+ 'install.mjs', 'install-metadata.mjs', 'mem-cli.mjs',
204
203
  ];
205
204
  const SWITCHABLE_PATHS = [...SOURCE_FILES, 'scripts', 'registry', 'node_modules'];
206
205
 
package/hook.mjs CHANGED
@@ -21,15 +21,11 @@ import {
21
21
  writePendingEntry, mergePendingEntries, episodeHasSignificantContent,
22
22
  } from './hook-episode.mjs';
23
23
  import { selectWithTokenBudget, updateClaudeMd, buildSummaryLines } from './hook-context.mjs';
24
- import { dispatchOnPreToolUse, dispatchOnUserPrompt } from './dispatch.mjs';
25
- import { collectFeedback } from './dispatch-feedback.mjs';
26
24
  import {
27
25
  RUNTIME_DIR, EPISODE_BUFFER_SIZE, EPISODE_TIME_GAP_MS,
28
26
  SESSION_EXPIRY_MS, STALE_SESSION_MS, STALE_LOCK_MS, FALLBACK_OBS_WINDOW_MS,
29
- RESOURCE_RESCAN_INTERVAL_MS,
30
- sessionFile, getSessionId, createSessionId, openDb, getRegistryDb,
31
- closeRegistryDb, spawnBackground, appendToolEvent, readAndClearToolEvents,
32
- resetInjectionBudget, hasInjectionBudget, incrementInjection,
27
+ sessionFile, getSessionId, createSessionId, openDb,
28
+ spawnBackground,
33
29
  } from './hook-shared.mjs';
34
30
  import { handleLLMEpisode, handleLLMSummary, saveObservation, buildImmediateObservation } from './hook-llm.mjs';
35
31
  import { searchRelevantMemories, recallForFile } from './hook-memory.mjs';
@@ -38,9 +34,9 @@ import { checkForUpdate } from './hook-update.mjs';
38
34
  import { SKIP_TOOLS, SKIP_PREFIXES } from './skip-tools.mjs';
39
35
 
40
36
  // Prevent recursive hooks from background claude -p calls
41
- // Background workers (llm-episode, llm-summary, resource-scan) are exempt — they're ours
37
+ // Background workers (llm-episode, llm-summary) are exempt — they're ours
42
38
  const event = process.argv[2];
43
- const BG_EVENTS = new Set(['llm-episode', 'llm-summary', 'resource-scan']);
39
+ const BG_EVENTS = new Set(['llm-episode', 'llm-summary']);
44
40
 
45
41
  // Respect Claude Code plugin disable state even when legacy settings.json hooks remain.
46
42
  // install.mjs writes direct hooks into ~/.claude/settings.json, so disabling the plugin
@@ -253,21 +249,6 @@ async function handlePostToolUse() {
253
249
 
254
250
  writeEpisode(episode);
255
251
 
256
- // Track feedback-relevant tool events for dispatch adoption detection.
257
- // Skill/Agent: adoption detection checks these tool names.
258
- // Edit/Write/NotebookEdit: outcome detection checks for edits.
259
- // Grep: investigation signal for debugging pattern detection.
260
- // Bash errors: outcome detection checks for error signals.
261
- // Bash test/build success: TDD and verification pattern detection.
262
- const isTrackableBash = tool_name === 'Bash' && (bashSig?.isError || bashSig?.isTest || bashSig?.isBuild);
263
- if (['Skill', 'Agent', 'Edit', 'Write', 'NotebookEdit', 'Grep'].includes(tool_name) || isTrackableBash) {
264
- appendToolEvent({
265
- tool_name,
266
- tool_input: toolInput,
267
- tool_response: (tool_name === 'Bash' && bashSig?.isSignificant) ? scrubSecrets(resp.slice(0, 500)) : '',
268
- timestamp: Date.now(),
269
- });
270
- }
271
252
  } finally {
272
253
  releaseLock();
273
254
  if (db) try { db.close(); } catch {}
@@ -396,18 +377,6 @@ async function handleStop() {
396
377
  }
397
378
  }
398
379
 
399
- // Dispatch: collect feedback on recommendations using actual tool events
400
- // PostToolUse tracks Skill/Task/Edit/Write/Bash events in a JSONL file.
401
- // These events drive adoption detection (Skill/Task) and outcome detection (Edit/Bash errors).
402
- // Always clear event file to prevent stale events accumulating if registry DB is unavailable.
403
- try {
404
- const sessionEvents = readAndClearToolEvents();
405
- const rdb = getRegistryDb();
406
- if (rdb) {
407
- await collectFeedback(rdb, sessionId, sessionEvents);
408
- }
409
- } catch (e) { debugCatch(e, 'handleStop-feedback'); }
410
-
411
380
  // Spawn background for session summary (pass sessionId and project)
412
381
  spawnBackground('llm-summary', sessionId, project);
413
382
 
@@ -418,8 +387,6 @@ async function handleStop() {
418
387
  // ─── SessionStart Handler + CLAUDE.md Persistence (Tier 1 A, E) ─────────────
419
388
 
420
389
  async function handleSessionStart() {
421
- resetInjectionBudget();
422
-
423
390
  // Snapshot episode BEFORE flush for handoff extraction
424
391
  const episodeSnapshot = readEpisodeRaw();
425
392
 
@@ -538,15 +505,6 @@ async function handleSessionStart() {
538
505
  ).get(prevProject || project, 'clear');
539
506
  } catch {}
540
507
 
541
- // Collect dispatch feedback for previous session
542
- try {
543
- const rdb = getRegistryDb();
544
- if (rdb) {
545
- const sessionEvents = readAndClearToolEvents();
546
- await collectFeedback(rdb, prevSessionId, sessionEvents);
547
- }
548
- } catch (e) { debugCatch(e, 'session-start-prev-feedback'); }
549
-
550
508
  // Generate session summary for previous session (background Haiku — richer version)
551
509
  spawnBackground('llm-summary', prevSessionId, prevProject || project);
552
510
 
@@ -759,13 +717,6 @@ async function handleSessionStart() {
759
717
  // CLAUDE.md: slim (summary + handoff state — observations already in stdout)
760
718
  updateClaudeMd([...summaryLines, ...handoffLines].join('\n'));
761
719
 
762
- // Background rescan: detect changed/new managed resources since last scan.
763
- // TTL-based (1h) — avoids redundant filesystem scans on every session.
764
- // Non-blocking: spawns detached worker, results available before first user prompt.
765
- if (needsResourceRescan()) {
766
- spawnBackground('resource-scan');
767
- }
768
-
769
720
  // Auto-update check (24h throttle, 3s timeout, silent on failure)
770
721
  // Fire-and-forget: don't block SessionStart for up to 3s network timeout
771
722
  checkForUpdate().then(updateResult => {
@@ -784,51 +735,6 @@ async function handleSessionStart() {
784
735
  }
785
736
  }
786
737
 
787
- // ─── PreToolUse Handler (Dispatch) ──────────────────────────────────────────
788
-
789
- async function handlePreToolUse() {
790
- let raw;
791
- try { raw = await readStdin(); } catch { return; }
792
-
793
- let hookData;
794
- try { hookData = JSON.parse(raw.text); } catch { return; }
795
-
796
- const rdb = getRegistryDb();
797
- if (!rdb) return;
798
-
799
- // Quick session context from user prompts DB + episode buffer
800
- const sessionId = getSessionId();
801
- const sessionCtx = { sessionId };
802
- const db = openDb();
803
- if (db) {
804
- try {
805
- const latest = db.prepare(
806
- 'SELECT prompt_text FROM user_prompts WHERE content_session_id = ? ORDER BY created_at_epoch DESC LIMIT 1'
807
- ).get(sessionId);
808
- if (latest) sessionCtx.userPrompt = latest.prompt_text;
809
- } catch (e) { debugCatch(e, 'handlePreToolUse-queryPrompt'); } finally { db.close(); }
810
- }
811
- // Collect recent file paths from episode buffer for tech stack inference
812
- try {
813
- const episode = readEpisodeRaw();
814
- if (episode?.entries) {
815
- const files = new Set();
816
- for (const e of episode.entries) {
817
- if (e.files) for (const f of e.files) files.add(f);
818
- }
819
- if (files.size > 0) sessionCtx.recentFiles = [...files].slice(-10);
820
- }
821
- } catch {}
822
-
823
- if (hasInjectionBudget()) {
824
- const injection = await dispatchOnPreToolUse(rdb, hookData, sessionCtx);
825
- if (injection) {
826
- process.stdout.write(injection + '\n');
827
- incrementInjection();
828
- }
829
- }
830
- }
831
-
832
738
  // ─── UserPromptSubmit Handler ────────────────────────────────────────────────
833
739
 
834
740
  async function handleUserPrompt() {
@@ -874,13 +780,12 @@ async function handleUserPrompt() {
874
780
  );
875
781
 
876
782
  // Cross-session handoff injection (first 3 prompts window, before semantic memory)
877
- if (counter?.prompt_counter <= 3 && hasInjectionBudget()) {
783
+ if (counter?.prompt_counter <= 3) {
878
784
  try {
879
785
  if (detectContinuationIntent(db, promptText, project)) {
880
786
  const injection = renderHandoffInjection(db, project);
881
787
  if (injection) {
882
788
  process.stdout.write(injection + '\n');
883
- incrementInjection();
884
789
  // Consume clear handoff after injection to prevent duplicate injection on prompts 2-3.
885
790
  // Exit handoffs are kept (7d TTL, content-dependent keyword/FTS matching won't re-trigger).
886
791
  try { db.prepare("DELETE FROM session_handoffs WHERE project = ? AND type = 'clear'").run(project); } catch {}
@@ -890,44 +795,29 @@ async function handleUserPrompt() {
890
795
  }
891
796
 
892
797
  // Semantic memory injection: search past observations for the user's prompt
893
- if (hasInjectionBudget()) {
894
- try {
895
- const keyObs = db.prepare(`
896
- SELECT id FROM observations
897
- WHERE project = ? AND COALESCE(compressed_into, 0) = 0
898
- AND COALESCE(importance, 1) >= 2
899
- ORDER BY created_at_epoch DESC LIMIT 5
900
- `).all(project);
901
- const keyContextIds = keyObs.map(o => o.id);
902
-
903
- const memories = searchRelevantMemories(db, promptText, project, keyContextIds);
904
- if (memories.length > 0) {
905
- const lines = ['<memory-context relevance="high">'];
906
- for (const m of memories) {
907
- const lessonTag = m.lesson_learned ? ` | Lesson: ${m.lesson_learned}` : '';
908
- lines.push(`- [${m.type}] ${truncate(m.title, 80)}${lessonTag} (#${m.id})`);
909
- }
910
- lines.push('</memory-context>');
911
- process.stdout.write(lines.join('\n') + '\n');
912
- incrementInjection();
798
+ try {
799
+ const keyObs = db.prepare(`
800
+ SELECT id FROM observations
801
+ WHERE project = ? AND COALESCE(compressed_into, 0) = 0
802
+ AND COALESCE(importance, 1) >= 2
803
+ ORDER BY created_at_epoch DESC LIMIT 5
804
+ `).all(project);
805
+ const keyContextIds = keyObs.map(o => o.id);
806
+
807
+ const memories = searchRelevantMemories(db, promptText, project, keyContextIds);
808
+ if (memories.length > 0) {
809
+ const lines = ['<memory-context relevance="high">'];
810
+ for (const m of memories) {
811
+ const lessonTag = m.lesson_learned ? ` | Lesson: ${m.lesson_learned}` : '';
812
+ lines.push(`- [${m.type}] ${truncate(m.title, 80)}${lessonTag} (#${m.id})`);
913
813
  }
914
- } catch (e) { debugCatch(e, 'handleUserPrompt-memory'); }
915
- }
814
+ lines.push('</memory-context>');
815
+ process.stdout.write(lines.join('\n') + '\n');
816
+ }
817
+ } catch (e) { debugCatch(e, 'handleUserPrompt-memory'); }
916
818
  } finally {
917
819
  db.close();
918
820
  }
919
-
920
- // Dispatch: only fires for explicit user requests ("I need X skill", "find me a tool for Y")
921
- try {
922
- const rdb = getRegistryDb();
923
- if (rdb && hasInjectionBudget()) {
924
- const result = await dispatchOnUserPrompt(rdb, promptText, sessionId);
925
- if (result) {
926
- process.stdout.write(result + '\n');
927
- incrementInjection();
928
- }
929
- }
930
- } catch (e) { debugCatch(e, 'handleUserPrompt-dispatch'); }
931
821
  }
932
822
 
933
823
  // ─── Auto-Compress (Background Worker) ───────────────────────────────────────
@@ -1001,77 +891,6 @@ function handleAutoCompress() {
1001
891
  }
1002
892
  }
1003
893
 
1004
- // ─── Resource Rescan (Background Worker) ─────────────────────────────────────
1005
-
1006
- const RESCAN_MARKER = join(RUNTIME_DIR, 'last-resource-scan');
1007
-
1008
- /**
1009
- * Check if resource rescan is needed (marker older than RESOURCE_RESCAN_INTERVAL_MS).
1010
- * @returns {boolean}
1011
- */
1012
- function needsResourceRescan() {
1013
- try {
1014
- const marker = statSync(RESCAN_MARKER);
1015
- return (Date.now() - marker.mtimeMs) > RESOURCE_RESCAN_INTERVAL_MS;
1016
- } catch {
1017
- return true; // No marker = never scanned
1018
- }
1019
- }
1020
-
1021
- /**
1022
- * Background worker: scan filesystem for changed resources, upsert any diffs.
1023
- * Spawned by SessionStart when marker is stale. Non-blocking to the hook caller.
1024
- */
1025
- async function handleResourceScan() {
1026
- const rdb = getRegistryDb();
1027
- if (!rdb) return;
1028
-
1029
- try {
1030
- const { scanAllResources, diffResources } = await import('./registry-scanner.mjs');
1031
- const { upsertResource } = await import('./registry.mjs');
1032
-
1033
- const scanned = scanAllResources();
1034
- const { toIndex, toDisable } = diffResources(rdb, scanned);
1035
-
1036
- if (toIndex.length === 0 && toDisable.length === 0) {
1037
- // Touch marker even if nothing changed — avoids rescanning every session
1038
- writeFileSync(RESCAN_MARKER, String(Date.now()));
1039
- return;
1040
- }
1041
-
1042
- // Upsert changed resources with fallback metadata (no Haiku)
1043
- let upsertErrors = 0;
1044
- for (const res of toIndex) {
1045
- try {
1046
- upsertResource(rdb, {
1047
- name: res.name,
1048
- type: res.type,
1049
- status: 'active',
1050
- source: res.source,
1051
- repo_url: res.repoUrl || null,
1052
- local_path: res.localPath,
1053
- file_hash: res.fileHash,
1054
- intent_tags: res.name.replace(/-/g, ' ').replace(/\//g, ' '),
1055
- trigger_patterns: `when user needs ${res.name.replace(/-/g, ' ').replace(/\//g, ' ')}`,
1056
- capability_summary: `${res.type}: ${res.name.replace(/-/g, ' ')}`,
1057
- });
1058
- } catch (e) { upsertErrors++; if (upsertErrors <= 3) debugCatch(e, `handleResourceScan-upsert[${upsertErrors}]`); }
1059
- }
1060
-
1061
- // Disable resources no longer on filesystem
1062
- for (const row of toDisable) {
1063
- try {
1064
- rdb.prepare("UPDATE resources SET status = 'disabled', updated_at = datetime('now') WHERE id = ?").run(row.id);
1065
- } catch {}
1066
- }
1067
-
1068
- debugLog('DEBUG', 'resource-scan', `indexed ${toIndex.length}, disabled ${toDisable.length}`);
1069
- writeFileSync(RESCAN_MARKER, String(Date.now()));
1070
- } catch (e) {
1071
- debugCatch(e, 'handleResourceScan');
1072
- }
1073
- }
1074
-
1075
894
  // ─── Utilities ──────────────────────────────────────────────────────────────
1076
895
 
1077
896
  function readStdin() {
@@ -1137,14 +956,12 @@ function normalizeToolResponse(toolResponse) {
1137
956
 
1138
957
  try {
1139
958
  switch (event) {
1140
- case 'pre-tool-use': await handlePreToolUse(); break;
1141
959
  case 'post-tool-use': await handlePostToolUse(); break;
1142
960
  case 'session-start': await handleSessionStart(); break;
1143
961
  case 'stop': await handleStop(); break;
1144
962
  case 'user-prompt': await handleUserPrompt(); break;
1145
963
  case 'llm-episode': await handleLLMEpisode(); break;
1146
964
  case 'llm-summary': await handleLLMSummary(); break;
1147
- case 'resource-scan': await handleResourceScan(); break;
1148
965
  case 'auto-compress': handleAutoCompress(); break;
1149
966
  }
1150
967
  } catch (err) {
@@ -1153,7 +970,4 @@ try {
1153
970
  console.error(`[claude-mem-lite] [${ts}] [ERROR] ${event}: ${err.message}`);
1154
971
  }
1155
972
 
1156
- // Close singleton registry DB to prevent WAL residue
1157
- closeRegistryDb();
1158
-
1159
973
  process.exit(0);
package/hooks/hooks.json CHANGED
@@ -18,18 +18,6 @@
18
18
  ]
19
19
  }
20
20
  ],
21
- "PreToolUse": [
22
- {
23
- "matcher": "*",
24
- "hooks": [
25
- {
26
- "type": "command",
27
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/hook.mjs\" pre-tool-use",
28
- "timeout": 2
29
- }
30
- ]
31
- }
32
- ],
33
21
  "PostToolUse": [
34
22
  {
35
23
  "matcher": "*",
@@ -58,6 +46,11 @@
58
46
  {
59
47
  "matcher": "*",
60
48
  "hooks": [
49
+ {
50
+ "type": "command",
51
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/user-prompt-search.js\"",
52
+ "timeout": 2
53
+ },
61
54
  {
62
55
  "type": "command",
63
56
  "command": "node \"${CLAUDE_PLUGIN_ROOT}/hook.mjs\" user-prompt",