claude-mem-lite 2.12.3 → 2.14.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +41 -78
- package/cli.mjs +10 -2
- package/commands/mem.md +15 -18
- package/commands/recall.md +9 -0
- package/commands/recent.md +7 -0
- package/commands/search.md +9 -0
- package/commands/timeline.md +7 -0
- package/hook-context.mjs +5 -5
- package/hook-llm.mjs +2 -2
- package/hook-memory.mjs +5 -5
- package/hook-semaphore.mjs +2 -1
- package/hook-shared.mjs +2 -87
- package/hook-update.mjs +2 -3
- package/hook.mjs +41 -215
- package/hooks/hooks.json +5 -12
- package/install-metadata.mjs +2072 -0
- package/install.mjs +39 -17
- package/mem-cli.mjs +583 -0
- package/package.json +9 -6
- package/registry-retriever.mjs +2 -2
- package/schema.mjs +23 -1
- package/scripts/user-prompt-search.js +284 -0
- package/server-internals.mjs +3 -3
- package/server.mjs +36 -44
- package/skip-tools.mjs +21 -0
- package/utils.mjs +26 -0
- package/dispatch-feedback.mjs +0 -382
- package/dispatch-inject.mjs +0 -213
- package/dispatch-patterns.mjs +0 -173
- package/dispatch-workflow.mjs +0 -170
- package/dispatch.mjs +0 -1239
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ The original sends **everything to the LLM and hopes it filters well**. claude-m
|
|
|
52
52
|
|
|
53
53
|
## Features
|
|
54
54
|
|
|
55
|
-
- **Automatic capture** -- Hooks into Claude Code lifecycle (PostToolUse,
|
|
55
|
+
- **Automatic capture** -- Hooks into Claude Code lifecycle (PostToolUse, SessionStart, Stop, UserPromptSubmit) to record observations without manual effort
|
|
56
56
|
- **FTS5 search** -- BM25-ranked full-text search across observations, session summaries, and user prompts with importance weighting
|
|
57
57
|
- **Timeline browsing** -- Navigate observations chronologically with anchor-based context windows
|
|
58
58
|
- **Episode batching** -- Groups related file operations into coherent episodes before LLM encoding
|
|
@@ -79,20 +79,19 @@ The original sends **everything to the LLM and hopes it filters well**. claude-m
|
|
|
79
79
|
- **Atomic writes** -- All file writes (episodes, CLAUDE.md) use write-to-tmp + rename to prevent corruption on crash
|
|
80
80
|
- **Robust locking** -- PID-aware lock files with automatic stale/orphan cleanup (>30s timeout or dead PID)
|
|
81
81
|
- **Stale session cleanup** -- Sessions active for >24h are automatically marked as abandoned on next start
|
|
82
|
-
- **
|
|
83
|
-
- **Resource registry** -- Indexes installed skills and agents with FTS5 search, composite scoring, and invocation tracking
|
|
82
|
+
- **Resource registry** -- Indexes installed skills and agents with FTS5 search, composite scoring, and invocation tracking; searchable via `mem_registry` MCP tool
|
|
84
83
|
- **Unified resource discovery** -- Shared filesystem traversal layer (`resource-discovery.mjs`) used by both runtime scanner and offline indexer, supporting flat directories, plugin nesting, and loose `.md` files
|
|
85
|
-
- **
|
|
86
|
-
- **Bilingual intent recognition** -- Understands user intent in both English and Chinese (15+ EN + 12+ CN intent categories)
|
|
87
|
-
- **Domain synonym expansion** -- Dispatch queries expand to domain synonyms (e.g., "fix" → debug, bugfix, troubleshoot, diagnose, repair)
|
|
88
|
-
- **DB-persisted cooldown** -- 5-minute cross-session cooldown and per-session dedup prevent repeated recommendations
|
|
84
|
+
- **Domain synonym expansion** -- Registry search queries expand to domain synonyms (e.g., "fix" → debug, bugfix, troubleshoot, diagnose, repair)
|
|
89
85
|
- **Dual LLM mode** -- Auto-detects `ANTHROPIC_API_KEY` for direct API calls; falls back to `claude -p` CLI when no key is available
|
|
90
|
-
- **
|
|
91
|
-
- **
|
|
86
|
+
- **Lesson-learned indexing** -- `lesson_learned` field indexed in FTS5 with weight 8, making past debugging insights directly searchable
|
|
87
|
+
- **Cross-source normalization** -- `mem_search` normalizes scores across observations, sessions, and prompts before merging, preventing any source from dominating results
|
|
88
|
+
- **Exponential recency decay** -- Type-differentiated half-lives (decisions: 90d, discoveries: 60d, bugfixes: 14d, changes: 7d) consistently applied in all ranking paths
|
|
89
|
+
- **Prompt-time memory injection** -- UserPromptSubmit hook automatically searches and injects relevant past observations with recency and importance weighting
|
|
90
|
+
- **Dual injection dedup** -- `user-prompt-search.js` and `handleUserPrompt` coordinate via temp file to prevent duplicate memory injection
|
|
92
91
|
- **Configurable LLM model** -- Switch between Haiku (fast/cheap) and Sonnet (deeper analysis) via `CLAUDE_MEM_MODEL` env var
|
|
93
92
|
- **DB auto-recovery** -- Detects and cleans corrupted WAL/SHM files on startup; periodic WAL checkpoints prevent unbounded growth
|
|
94
93
|
- **Schema auto-migration** -- Idempotent `ALTER TABLE` migrations run on every startup, safely adding new columns and indexes without data loss
|
|
95
|
-
- **Exploration bonus** -- New resources in the registry get a fair chance in composite ranking; zombie resources (high recommend, zero adopt) are penalized
|
|
94
|
+
- **Exploration bonus** -- New resources in the registry get a fair chance in composite ranking; zombie resources (high recommend, zero adopt) are penalized in scoring
|
|
96
95
|
- **LLM concurrency control** -- File-based semaphore limits background workers to 2 concurrent LLM calls, preventing resource contention
|
|
97
96
|
- **stdin overflow protection** -- Hook input truncated at 256KB with regex-based action salvage for oversized tool outputs
|
|
98
97
|
- **Cross-session handoff** -- Captures session state (request, completed work, next steps, key files) on `/clear` or `/exit`, then injects context when the next session detects continuation intent via explicit keywords or FTS5 term overlap
|
|
@@ -144,8 +143,8 @@ Source files stay in the cloned repo. Update via `git pull && node install.mjs i
|
|
|
144
143
|
### What happens during installation
|
|
145
144
|
|
|
146
145
|
1. **Install dependencies** -- `npm install --omit=dev` (compiles native `better-sqlite3`)
|
|
147
|
-
2. **Register MCP server** -- `mem` server with
|
|
148
|
-
3. **Configure hooks** -- `PostToolUse`, `
|
|
146
|
+
2. **Register MCP server** -- `mem` server with 9 tools (search, timeline, get, save, stats, delete, compress, maintain, registry)
|
|
147
|
+
3. **Configure hooks** -- `PostToolUse`, `SessionStart`, `Stop`, `UserPromptSubmit` lifecycle hooks
|
|
149
148
|
4. **Create data directory** -- `~/.claude-mem-lite/` (hidden) for database, runtime, and managed resource files
|
|
150
149
|
5. **Auto-migrate** -- If `~/.claude-mem/` (original claude-mem) or `~/claude-mem-lite/` (pre-v0.5 unhidden) exists, migrates database and runtime files to `~/.claude-mem-lite/`, preserving the original untouched
|
|
151
150
|
6. **Initialize database** -- SQLite with WAL mode, FTS5 indexes created on first server start
|
|
@@ -203,6 +202,8 @@ rm -rf ~/claude-mem-lite/ # pre-v0.5 unhidden (if not auto-moved)
|
|
|
203
202
|
| `mem_stats` | View statistics: counts, type distribution, top projects, daily activity. |
|
|
204
203
|
| `mem_delete` | Delete observations by ID with preview/confirm workflow. FTS5 cleanup is automatic. |
|
|
205
204
|
| `mem_compress` | Compress old low-value observations into weekly summaries to reduce noise. |
|
|
205
|
+
| `mem_maintain` | Memory maintenance: scan for duplicates/stale/broken items, then execute cleanup/dedup operations. |
|
|
206
|
+
| `mem_registry` | Manage resource registry: search for skills/agents by need, list resources, view stats, import/remove tools, reindex. |
|
|
206
207
|
|
|
207
208
|
### Skill Commands (in Claude Code chat)
|
|
208
209
|
|
|
@@ -231,13 +232,15 @@ Five core tables with FTS5 virtual tables for search:
|
|
|
231
232
|
```
|
|
232
233
|
id, memory_session_id, project, type, title, subtitle,
|
|
233
234
|
text, narrative, concepts, facts, files_read, files_modified,
|
|
234
|
-
importance, related_ids, created_at, created_at_epoch
|
|
235
|
+
importance, related_ids, created_at, created_at_epoch,
|
|
236
|
+
lesson_learned, minhash_sig, access_count, compressed_into, search_aliases
|
|
235
237
|
```
|
|
236
238
|
|
|
237
239
|
**session_summaries** -- LLM-generated session summaries
|
|
238
240
|
```
|
|
239
241
|
id, memory_session_id, project, request, investigated,
|
|
240
|
-
learned, completed, next_steps, files_read, files_edited, notes
|
|
242
|
+
learned, completed, next_steps, files_read, files_edited, notes,
|
|
243
|
+
remaining_items, lessons, key_decisions
|
|
241
244
|
```
|
|
242
245
|
|
|
243
246
|
**sdk_sessions** -- Session tracking
|
|
@@ -257,7 +260,7 @@ project, type, session_id, working_on, completed, unfinished,
|
|
|
257
260
|
key_files, key_decisions, match_keywords, created_at_epoch
|
|
258
261
|
```
|
|
259
262
|
|
|
260
|
-
FTS5 indexes: `observations_fts
|
|
263
|
+
FTS5 indexes: `observations_fts` (title, subtitle, narrative, text, facts, concepts, lesson_learned), `session_summaries_fts`, `user_prompts_fts`
|
|
261
264
|
|
|
262
265
|
## How It Works
|
|
263
266
|
|
|
@@ -270,7 +273,6 @@ SessionStart
|
|
|
270
273
|
-> Clean orphaned/stale lock files
|
|
271
274
|
-> Query recent observations (24h)
|
|
272
275
|
-> Inject context into CLAUDE.md + stdout
|
|
273
|
-
-> Dispatch: recommend skill/agent based on user prompt (Tier 0→1→2→3)
|
|
274
276
|
|
|
275
277
|
PostToolUse (every tool execution)
|
|
276
278
|
-> Bash pre-filter skips noise in ~5ms (Read paths tracked to reads file)
|
|
@@ -282,75 +284,36 @@ PostToolUse (every tool execution)
|
|
|
282
284
|
-> Spawn LLM episode worker for significant episodes
|
|
283
285
|
-> Error-triggered recall: search memory for related past fixes
|
|
284
286
|
|
|
285
|
-
|
|
286
|
-
->
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
-> Capture user prompt text to user_prompts table
|
|
290
|
-
-> Increment session prompt counter
|
|
291
|
-
-> Handoff: detect continuation intent → inject previous session context
|
|
292
|
-
->
|
|
293
|
-
-> Primary dispatch point — user intent is clearest here
|
|
287
|
+
UserPromptSubmit (two parallel paths)
|
|
288
|
+
-> [user-prompt-search.js] Auto-search memory via FTS5 + active file context
|
|
289
|
+
-> [user-prompt-search.js] Inject relevant past observations with recency/importance weighting
|
|
290
|
+
-> [user-prompt-search.js] Write injected IDs to temp file for dedup
|
|
291
|
+
-> [hook.mjs handleUserPrompt] Capture user prompt text to user_prompts table
|
|
292
|
+
-> [hook.mjs handleUserPrompt] Increment session prompt counter
|
|
293
|
+
-> [hook.mjs handleUserPrompt] Handoff: detect continuation intent → inject previous session context
|
|
294
|
+
-> [hook.mjs handleUserPrompt] Semantic memory injection (hook-memory.mjs), deduped via temp file
|
|
294
295
|
|
|
295
296
|
Stop
|
|
296
297
|
-> Flush final episode buffer
|
|
297
298
|
-> Save handoff snapshot (on /exit)
|
|
298
|
-
-> Collect dispatch feedback: adoption detection + outcome scoring
|
|
299
299
|
-> Mark session completed
|
|
300
300
|
-> Spawn LLM summary worker (poll-based wait)
|
|
301
301
|
```
|
|
302
302
|
|
|
303
|
-
###
|
|
303
|
+
### Resource Registry
|
|
304
304
|
|
|
305
|
-
The
|
|
305
|
+
The resource registry (`registry.mjs`, `registry-retriever.mjs`) indexes installed skills and agents into a searchable FTS5 database. Unlike the previous proactive dispatch system, the registry is now on-demand — Claude searches it via the `mem_registry` MCP tool when it needs to discover relevant skills or agents.
|
|
306
306
|
|
|
307
307
|
```
|
|
308
|
-
|
|
309
|
-
->
|
|
310
|
-
->
|
|
311
|
-
->
|
|
312
|
-
->
|
|
313
|
-
|
|
314
|
-
Tier 1: Context Signal Extraction (<1ms)
|
|
315
|
-
-> Intent: extract from user prompt (test, fix, deploy, review...)
|
|
316
|
-
-> Tech stack: infer from recent file extensions (.ts → typescript)
|
|
317
|
-
-> Action: infer from tool name (Edit → edit, Bash+jest → test)
|
|
318
|
-
-> Error domain: classify errors (type-error, test-fail, build-fail...)
|
|
319
|
-
|
|
320
|
-
Tier 2: FTS5 Retrieval (<5ms)
|
|
321
|
-
-> Expand signals with domain synonyms (15+ EN, 12+ CN categories)
|
|
322
|
-
-> BM25-ranked search over resource registry
|
|
323
|
-
-> Composite scoring: BM25 (40%) + repo stars (15%) + success rate (15%) + adoption rate (10%)
|
|
324
|
-
|
|
325
|
-
Tier 3: Haiku Semantic Dispatch (~500ms, SessionStart only)
|
|
326
|
-
-> Activated when FTS5 confidence is low or top results are ambiguous
|
|
327
|
-
-> LLM generates semantic search query for refined retrieval
|
|
328
|
-
-> Disabled for PreToolUse (2s hook timeout insufficient)
|
|
308
|
+
Registry pipeline:
|
|
309
|
+
-> registry-scanner.mjs discovers skills/agents on filesystem
|
|
310
|
+
-> resource-discovery.mjs handles flat dirs, plugin nesting, loose .md files
|
|
311
|
+
-> registry-indexer.mjs indexes content into FTS5 with metadata
|
|
312
|
+
-> registry-retriever.mjs provides BM25-ranked search with synonym expansion
|
|
313
|
+
-> mem_registry MCP tool exposes search/list/stats/import/remove/reindex actions
|
|
329
314
|
```
|
|
330
315
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
| Hook | Budget | Tiers | Use case |
|
|
334
|
-
|------|--------|-------|----------|
|
|
335
|
-
| SessionStart | 10s | 0→1→2→3 | Analyze previous session's next_steps, suggest skill/agent upfront |
|
|
336
|
-
| UserPromptSubmit | 2s | 0→1→2 | Primary dispatch point — user's actual prompt has clearest intent |
|
|
337
|
-
| PreToolUse | 2s | 0→1→2 | React to current action context in real-time |
|
|
338
|
-
|
|
339
|
-
**Feedback loop (Stop hook):**
|
|
340
|
-
|
|
341
|
-
At session end, the system reviews all recommendations made during the session:
|
|
342
|
-
- **Adoption detection** -- Did Claude actually use the recommended skill (`Skill` tool) or agent (`Task` tool)?
|
|
343
|
-
- **Outcome detection** -- Was the session successful (edits without errors), partial (errors then fixes), or failed?
|
|
344
|
-
- **Score calculation** -- Adopted + success = 1.0, adopted + partial = 0.5, adopted + failure = 0.2
|
|
345
|
-
- Stats feed back into composite scoring, improving future dispatch quality over time
|
|
346
|
-
|
|
347
|
-
**Injection templates:**
|
|
348
|
-
|
|
349
|
-
| Resource type | Location | Template |
|
|
350
|
-
|---------------|----------|----------|
|
|
351
|
-
| Skill | `~/.claude/skills/` (native) | Short hint: use `/skill <name>` |
|
|
352
|
-
| Skill | managed directory | Full skill content injected (up to 3KB) |
|
|
353
|
-
| Agent | any | Agent definition injected for `Task` tool delegation |
|
|
316
|
+
Composite scoring for search results: BM25 relevance (40%) + repo stars (15%) + success rate (15%) + adoption rate (10%) + freshness (10%) + exploration bonus (10%). Domain filtering ensures platform-specific resources (iOS, Go, Rust) only surface for matching projects.
|
|
354
317
|
|
|
355
318
|
### Episode Encoding
|
|
356
319
|
|
|
@@ -438,16 +401,15 @@ claude-mem-lite/
|
|
|
438
401
|
hook-handoff.mjs # Cross-session handoff: state extraction, intent detection, injection
|
|
439
402
|
hook-context.mjs # CLAUDE.md context injection and token budgeting
|
|
440
403
|
hook-episode.mjs # Episode buffer management: atomic writes, pending entry merging
|
|
404
|
+
hook-memory.mjs # Semantic memory injection on user prompt
|
|
441
405
|
hook-semaphore.mjs # LLM concurrency control: file-based semaphore for background workers
|
|
442
406
|
schema.mjs # Database schema: single source of truth for tables, migrations, FTS5
|
|
443
407
|
tool-schemas.mjs # Shared Zod schemas for MCP tool validation
|
|
444
|
-
utils.mjs # Shared utilities: FTS5 query building, MinHash dedup, secret scrubbing
|
|
445
|
-
#
|
|
446
|
-
dispatch.mjs # 3-tier dispatch orchestration: fast filter, context signals, FTS5, Haiku
|
|
447
|
-
dispatch-inject.mjs # Injection template rendering for skill/agent recommendations
|
|
448
|
-
dispatch-feedback.mjs # Closed-loop feedback: adoption detection, outcome tracking
|
|
408
|
+
utils.mjs # Shared utilities: FTS5 query building, BM25 weight constants, MinHash dedup, secret scrubbing
|
|
409
|
+
# Resource registry
|
|
449
410
|
registry.mjs # Resource registry DB: schema, CRUD, FTS5, invocation tracking
|
|
450
411
|
registry-retriever.mjs # FTS5 retrieval with synonym expansion and composite scoring
|
|
412
|
+
registry-indexer.mjs # Resource indexing pipeline
|
|
451
413
|
registry-scanner.mjs # Filesystem scanner: reads content + hashes, delegates discovery
|
|
452
414
|
resource-discovery.mjs # Shared discovery layer: flat dirs, plugin nesting, loose .md files
|
|
453
415
|
haiku-client.mjs # Unified Haiku LLM wrapper: direct API or CLI fallback
|
|
@@ -458,6 +420,7 @@ claude-mem-lite/
|
|
|
458
420
|
scripts/
|
|
459
421
|
setup.sh # Setup hook: npm install + migration (hidden dir + old dir)
|
|
460
422
|
post-tool-use.sh # Bash pre-filter: skips noise in ~5ms, tracks Read paths
|
|
423
|
+
user-prompt-search.js # UserPromptSubmit hook: auto-search memory on user prompts
|
|
461
424
|
convert-commands.mjs # Converts command .md → SKILL.md in managed plugins
|
|
462
425
|
index-managed.mjs # Offline indexer for managed resources
|
|
463
426
|
# Test & benchmark (dev only)
|
|
@@ -495,7 +458,7 @@ npm run benchmark:gate # CI gate: fails if metrics regress beyond 5% toleranc
|
|
|
495
458
|
| Variable | Description | Default |
|
|
496
459
|
|----------|-------------|---------|
|
|
497
460
|
| `CLAUDE_MEM_DIR` | Custom data directory. All databases, runtime files, and managed resources are stored here. | `~/.claude-mem-lite/` |
|
|
498
|
-
| `CLAUDE_MEM_MODEL` | LLM model for background calls (episode extraction, session summaries
|
|
461
|
+
| `CLAUDE_MEM_MODEL` | LLM model for background calls (episode extraction, session summaries). Accepts `haiku` or `sonnet`. | `haiku` |
|
|
499
462
|
| `CLAUDE_MEM_DEBUG` | Enable debug logging (`1` to enable). | _(disabled)_ |
|
|
500
463
|
|
|
501
464
|
## License
|
package/cli.mjs
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
const CLI_COMMANDS = new Set(['search', 'recent', 'recall', 'get', 'timeline', 'save', 'stats', 'context', 'help']);
|
|
3
3
|
|
|
4
|
-
|
|
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
|
|
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
|
|
12
|
-
- `/mem recent [n]` — Show recent N observations (default
|
|
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>` →
|
|
31
|
-
- `/mem recent` or `/mem recent 20` →
|
|
32
|
-
- `/mem
|
|
33
|
-
- `/mem
|
|
34
|
-
- `/mem
|
|
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,
|
|
35
|
+
- `/mem <query>` (no subcommand) → treat as search, run `claude-mem-lite search <query>` via Bash
|
|
39
36
|
|
|
40
|
-
|
|
37
|
+
Use Bash commands first. For detailed data, use `claude-mem-lite get <id>` via Bash.
|
|
@@ -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.
|
package/hook-context.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
import { readFileSync, writeFileSync, renameSync, unlinkSync } from 'fs';
|
|
6
|
-
import { estimateTokens, truncate, debugLog, debugCatch } from './utils.mjs';
|
|
6
|
+
import { estimateTokens, truncate, debugLog, debugCatch, DECAY_HALF_LIFE_BY_TYPE, DEFAULT_DECAY_HALF_LIFE_MS } from './utils.mjs';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Infer the project directory from environment variables or cwd.
|
|
@@ -83,9 +83,10 @@ export function selectWithTokenBudget(db, project, budget = 2000) {
|
|
|
83
83
|
let totalTokens = 0;
|
|
84
84
|
|
|
85
85
|
// Score each candidate: value = recency * importance, cost = tokens
|
|
86
|
+
// Recency uses exponential half-life (consistent with server.mjs BM25 scoring)
|
|
86
87
|
const scoredObs = obsPool.map(o => {
|
|
87
|
-
const
|
|
88
|
-
const recency = 1
|
|
88
|
+
const halfLifeMs = DECAY_HALF_LIFE_BY_TYPE[o.type] || DEFAULT_DECAY_HALF_LIFE_MS;
|
|
89
|
+
const recency = 1.0 + Math.exp(-0.693 * (now_ms - o.created_at_epoch) / halfLifeMs);
|
|
89
90
|
const impBoost = 0.5 + 0.5 * (o.importance || 1);
|
|
90
91
|
const lessonBoost = o.lesson_learned ? 1.3 : 1.0;
|
|
91
92
|
const value = recency * impBoost * lessonBoost;
|
|
@@ -94,8 +95,7 @@ export function selectWithTokenBudget(db, project, budget = 2000) {
|
|
|
94
95
|
});
|
|
95
96
|
|
|
96
97
|
const scoredSess = sessPool.map(s => {
|
|
97
|
-
const
|
|
98
|
-
const recency = 1 / (1 + ageDays);
|
|
98
|
+
const recency = 1.0 + Math.exp(-0.693 * (now_ms - s.created_at_epoch) / DEFAULT_DECAY_HALF_LIFE_MS);
|
|
99
99
|
const value = recency * 1.5; // Session summaries slightly boosted
|
|
100
100
|
const cost = estimateTokens((s.request || '') + (s.completed || '') + (s.next_steps || ''));
|
|
101
101
|
return { ...s, value, cost, valueDensity: cost > 0 ? value / Math.sqrt(cost) : 0 };
|
package/hook-llm.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { existsSync, readFileSync, unlinkSync, readdirSync } from 'fs';
|
|
|
6
6
|
import {
|
|
7
7
|
jaccardSimilarity, truncate, clampImportance, computeRuleImportance,
|
|
8
8
|
inferProject, parseJsonFromLLM,
|
|
9
|
-
computeMinHash, estimateJaccardFromMinHash, cjkBigrams, EDIT_TOOLS, debugCatch, debugLog,
|
|
9
|
+
computeMinHash, estimateJaccardFromMinHash, cjkBigrams, EDIT_TOOLS, debugCatch, debugLog, OBS_BM25,
|
|
10
10
|
} from './utils.mjs';
|
|
11
11
|
import { acquireLLMSlot, releaseLLMSlot } from './hook-semaphore.mjs';
|
|
12
12
|
import {
|
|
@@ -139,7 +139,7 @@ function linkRelatedObservations(db, savedId, obs, episode) {
|
|
|
139
139
|
SELECT o.id FROM observations_fts
|
|
140
140
|
JOIN observations o ON observations_fts.rowid = o.id
|
|
141
141
|
WHERE observations_fts MATCH ? AND o.id != ? AND o.project = ?
|
|
142
|
-
ORDER BY
|
|
142
|
+
ORDER BY ${OBS_BM25}
|
|
143
143
|
LIMIT 5
|
|
144
144
|
`).all(ftsQuery, newObs.id, episode.project);
|
|
145
145
|
for (const m of ftsMatches) candidates.add(m.id);
|
package/hook-memory.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// claude-mem-lite — Semantic Memory Injection
|
|
2
2
|
// Search past observations for relevant memories to inject as context at user-prompt time.
|
|
3
3
|
|
|
4
|
-
import { sanitizeFtsQuery, debugCatch } from './utils.mjs';
|
|
4
|
+
import { sanitizeFtsQuery, debugCatch, OBS_BM25 } from './utils.mjs';
|
|
5
5
|
|
|
6
6
|
const MAX_MEMORY_INJECTIONS = 3;
|
|
7
7
|
const MEMORY_LOOKBACK_MS = 60 * 86400000; // 60 days
|
|
@@ -32,7 +32,7 @@ export function searchRelevantMemories(db, userPrompt, project, excludeIds = [])
|
|
|
32
32
|
// Phase 1: Same-project search (highest priority)
|
|
33
33
|
const selectStmt = db.prepare(`
|
|
34
34
|
SELECT o.id, o.type, o.title, o.importance, o.lesson_learned, o.project,
|
|
35
|
-
|
|
35
|
+
${OBS_BM25} as relevance
|
|
36
36
|
FROM observations_fts
|
|
37
37
|
JOIN observations o ON o.id = observations_fts.rowid
|
|
38
38
|
WHERE observations_fts MATCH ?
|
|
@@ -40,7 +40,7 @@ export function searchRelevantMemories(db, userPrompt, project, excludeIds = [])
|
|
|
40
40
|
AND o.importance >= 1
|
|
41
41
|
AND o.created_at_epoch > ?
|
|
42
42
|
AND COALESCE(o.compressed_into, 0) = 0
|
|
43
|
-
ORDER BY
|
|
43
|
+
ORDER BY ${OBS_BM25}
|
|
44
44
|
LIMIT 10
|
|
45
45
|
`);
|
|
46
46
|
const rows = selectStmt.all(ftsQuery, project, cutoff);
|
|
@@ -51,7 +51,7 @@ export function searchRelevantMemories(db, userPrompt, project, excludeIds = [])
|
|
|
51
51
|
try {
|
|
52
52
|
crossRows = db.prepare(`
|
|
53
53
|
SELECT o.id, o.type, o.title, o.importance, o.lesson_learned, o.project,
|
|
54
|
-
|
|
54
|
+
${OBS_BM25} as relevance
|
|
55
55
|
FROM observations_fts
|
|
56
56
|
JOIN observations o ON o.id = observations_fts.rowid
|
|
57
57
|
WHERE observations_fts MATCH ?
|
|
@@ -60,7 +60,7 @@ export function searchRelevantMemories(db, userPrompt, project, excludeIds = [])
|
|
|
60
60
|
AND o.importance >= 2
|
|
61
61
|
AND o.created_at_epoch > ?
|
|
62
62
|
AND COALESCE(o.compressed_into, 0) = 0
|
|
63
|
-
ORDER BY
|
|
63
|
+
ORDER BY ${OBS_BM25}
|
|
64
64
|
LIMIT 5
|
|
65
65
|
`).all(ftsQuery, project, cutoff);
|
|
66
66
|
} catch (e) { debugCatch(e, 'crossProjectSearch'); }
|
package/hook-semaphore.mjs
CHANGED
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,
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from 'fs';
|
|
8
8
|
import { inferProject, debugCatch } from './utils.mjs';
|
|
9
|
-
import { ensureDb, DB_DIR
|
|
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
|
-
'
|
|
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
|
|