claude-memory-hub 0.8.0 → 0.8.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,48 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
5
5
 
6
6
  ---
7
7
 
8
+ ## [0.8.1] - 2026-04-02
9
+
10
+ Token-budget-aware MCP tools + proactive mid-session memory retrieval.
11
+
12
+ ### Token Budget Management
13
+
14
+ - **`max_tokens` parameter** — added to `memory_recall`, `memory_search`, `memory_fetch` MCP tools. When set, output is truncated to fit within the specified token budget (~4 chars/token). Helps Claude manage context window when many tools compete for space
15
+ - **`truncateToTokenBudget()` utility** — shared truncation function with `[...truncated to fit ~N token budget]` suffix
16
+
17
+ ### Proactive Memory Retrieval
18
+
19
+ - **Topic-shift detection** — PostToolUse hook now monitors file activity and detects when conversation drifts to a new domain (e.g., auth → payment → migration). Detection uses directory clustering + keyword matching across recent files
20
+ - **Mid-session context injection** — when topic shift detected, hook searches L3 for relevant past context and returns `additionalContext` via stdout JSON. Claude Code injects this into the conversation automatically
21
+ - **Trigger conditions:** every 15 tool calls OR on Bash errors after warmup (5+ calls)
22
+ - **State tracking** — per-session state at `~/.claude-memory-hub/proactive/<session_id>.json`, cleaned up on session end
23
+ - **Injection cap:** ~375 tokens (1500 chars) per injection, deduplicated by topic
24
+
25
+ ### Session End Improvements
26
+
27
+ - **Batch queue flush on session end** — `tryFlush()` called during Stop hook to prevent data loss from unflushed batch events
28
+ - **Proactive state cleanup** — per-session state files removed on session end
29
+
30
+ ### Research Findings (documented, no code changes needed)
31
+
32
+ Based on deep Claude Code source analysis:
33
+ - **Resource filtering:** Claude Code already defers MCP tools automatically via `isDeferredTool()`. Skill listings have budget system (`SKILL_BUDGET_CONTEXT_PERCENT=1%`). No external filtering needed
34
+ - **Multi-agent sharing:** Subagents inherit parent MCP servers via `initializeAgentMcpServers()`. Memory sharing via `memory_recall` works out-of-box — zero implementation needed
35
+ - **Permission-aware:** PostToolUse hook only fires for approved tools. Denied tools fire separate `PermissionDenied` hook. memory-hub is already permission-aware by design
36
+ - **IDE context:** Available as attachments in conversation (ide_selection, ide_opened_file) but not in hook inputs directly. Entity extraction captures file activity indirectly
37
+
38
+ ### Modified Files
39
+
40
+ ```
41
+ src/mcp/tool-definitions.ts — max_tokens param on 3 tools
42
+ src/mcp/tool-handlers.ts — truncateToTokenBudget() utility
43
+ src/retrieval/proactive-retrieval.ts — NEW: topic detection + injection
44
+ src/hooks-entry/post-tool-use.ts — proactive retrieval integration
45
+ src/hooks-entry/session-end.ts — batch flush + proactive cleanup
46
+ ```
47
+
48
+ ---
49
+
8
50
  ## [0.8.0] - 2026-04-02
9
51
 
10
52
  Major release: test infrastructure, architectural fixes, hook performance, data portability.
package/README.md CHANGED
@@ -19,7 +19,7 @@ Zero API key. Zero Python. Zero config. One install command.
19
19
 
20
20
  ## The Problem
21
21
 
22
- Claude Code forgets everything between sessions. Within long sessions, auto-compact destroys 90% of context. Every session wastes tokens loading resources that aren't needed. Search is keyword-only with no ranking.
22
+ Claude Code forgets everything between sessions. Within long sessions, auto-compact destroys 90% of context. Search is keyword-only with no ranking.
23
23
 
24
24
  ```
25
25
  Session 1: You spend 2 hours building auth system
@@ -29,37 +29,31 @@ Long session: Claude auto-compacts at 200K tokens
29
29
  → 180K tokens of context vaporized
30
30
  → Claude loses track of files, decisions, errors
31
31
 
32
- Every session: ALL skills + agents + rules loaded
33
- → 23-51K tokens consumed before you type anything
34
- → No external tool can prevent this (Claude Code limitation)
35
- → But you CAN identify and remove unused resources
36
-
37
32
  Search: Keyword-only, no semantic ranking
38
33
  → Irrelevant results, wasted tokens on full records
39
34
  ```
40
35
 
41
- **Four problems. memory-hub solves three directly and provides analysis for the fourth.**
42
-
43
36
  | Problem | Claude Code built-in | claude-mem | memory-hub |
44
37
  |---------|:-------------------:|:----------:|:----------:|
45
38
  | Cross-session memory | -- | Yes | **Yes** |
46
39
  | Influence what compact preserves | -- | -- | **Yes** |
47
- | Save compact output | -- | -- | **Yes** |
48
- | Token overhead analysis | -- | -- | **Yes** |
49
- | Semantic search (embeddings) | -- | Chroma (external) | **Yes (offline)** |
40
+ | Save compact output to L3 | -- | -- | **Yes** |
50
41
  | Hybrid search (FTS5 + TF-IDF + semantic) | -- | Partial | **Yes** |
51
42
  | 3-layer progressive search | -- | Yes | **Yes** |
52
43
  | Resource overhead analysis | -- | -- | **Yes** |
53
44
  | CLAUDE.md rule tracking | -- | -- | **Yes** |
54
- | Free-form observation capture | -- | Yes | **Yes** |
45
+ | Observation capture (14 patterns) | -- | Yes | **Yes** |
55
46
  | LLM summarization (3-tier) | -- | Yes (API) | **Yes (free)** |
47
+ | Token-budget-aware tools (`max_tokens`) | -- | -- | **Yes** |
48
+ | Proactive mid-session retrieval | -- | -- | **Yes** |
49
+ | Multi-agent memory sharing | -- | -- | **Yes (free)** |
50
+ | Permission-aware (approved only) | -- | -- | **Yes** |
51
+ | Data export/import (JSONL) | -- | -- | **Yes** |
52
+ | Hook batching (3ms vs 75ms) | -- | -- | **Yes** |
56
53
  | Browser UI | -- | Yes | **Yes** |
57
- | Health monitoring | -- | -- | **Yes** |
58
- | Migrate from claude-mem | N/A | N/A | **Yes** |
59
- | No API key needed | N/A | Yes | **Yes** |
60
- | No Python/Chroma needed | N/A | -- | **Yes** |
61
- | No XML format required | N/A | -- | **Yes** |
62
- | No HTTP server to manage | N/A | -- | **Yes** |
54
+ | Health monitoring + auto-cleanup | -- | -- | **Yes** |
55
+ | Unit tests (91 tests) | N/A | -- | **Yes** |
56
+ | No API key / Python / Chroma | N/A | Partial | **Yes** |
63
57
 
64
58
  ---
65
59
 
@@ -75,6 +69,8 @@ Claude makes a decision → memory-hub records: decision text + importance score
75
69
  ```
76
70
 
77
71
  No XML. No special format. Extracted directly from hook JSON metadata.
72
+ PostToolUse events are batched via write-through queue (~3ms per event vs ~75ms direct).
73
+ Mid-session topic shifts auto-inject relevant past context (proactive retrieval).
78
74
 
79
75
  ### Layer 2 — Compact Interceptor (the key innovation)
80
76
 
@@ -99,41 +95,21 @@ No XML. No special format. Extracted directly from hook JSON metadata.
99
95
  zero information loss
100
96
  ```
101
97
 
102
- **This is something no other memory tool does.** claude-mem never sees the compact. Built-in session memory is supplementary, not directive. memory-hub is the only system that **tells the compact what matters**.
98
+ **No other memory tool does this.** memory-hub is the only system that **tells the compact what matters**.
103
99
 
104
100
  ### Layer 3 — Cross-Session Memory
105
101
 
106
102
  ```
107
- Session N ends → rule-based summary from entities SQLite L3
108
- OR PostCompact summary (richer) SQLite L3
103
+ Session N ends → 3-tier summarization: PostCompact > CLI claude > rule-based
104
+ Summary saved to SQLite L3 with FTS5 indexing
109
105
 
110
106
  Session N+1 → UserPromptSubmit hook fires
111
- → FTS5 + TF-IDF hybrid search: match user prompt
112
- inject relevant context automatically
107
+ → FTS5 + TF-IDF + semantic search: match user prompt
108
+ Inject relevant past context automatically
113
109
  → Claude starts with history, not from zero
114
110
  ```
115
111
 
116
- ### Layer 4 — Resource Intelligence & Overhead Analysis
117
-
118
- ```
119
- ResourceRegistry scans your setup:
120
- 58 skills, 36 agents, 65 commands, 10 workflows, CLAUDE.md chain
121
-
122
- ResourceTracker records actual usage per session:
123
- "skill:mobile-development used 4/5 recent sessions"
124
- "agent:veo3-prompt-expert used 0/5 recent sessions"
125
-
126
- OverheadReport identifies waste:
127
- "42/58 skills never used → ~1500 listing tokens overhead"
128
- "CLAUDE.md chain is 8200 tokens → consider consolidating"
129
-
130
- UserPromptSubmit injects priority hints:
131
- "Frequently-used: skill:debugging, agent:planner, agent:tester"
132
- ```
133
-
134
- > **Transparency note:** Claude Code loads ALL resources into its system prompt — no external tool can prevent this. memory-hub provides **analysis and prioritization**, not filtering. To actually reduce token overhead, remove or relocate unused skills/agents based on the overhead report.
135
-
136
- ### Layer 5 — 3-Layer Progressive Search + Semantic (new in v0.5/v0.6)
112
+ ### Layer 4 — 3-Layer Progressive Search
137
113
 
138
114
  ```
139
115
  Traditional search: query → ALL full records → 5000+ tokens wasted
@@ -146,34 +122,33 @@ memory-hub search: query → Layer 1 (index) → ~50 tokens/result
146
122
  Token savings: ~80-90% vs. full context
147
123
  ```
148
124
 
149
- Hybrid ranking: FTS5 BM25 (keyword) + TF-IDF (term frequency) + **semantic cosine similarity** (384-dim embeddings, v0.6). "debugging tips" now matches "error fixing" even without shared keywords.
125
+ Hybrid ranking: FTS5 BM25 (keyword) + TF-IDF (term frequency) + semantic cosine similarity (384-dim embeddings). "debugging tips" matches "error fixing" even without shared keywords.
150
126
 
151
- ### Layer 6 — Resource Intelligence (new in v0.6)
127
+ ### Layer 5 — Resource Intelligence
152
128
 
153
129
  ```
154
130
  ResourceRegistry scans ALL .claude locations:
155
- ~/.claude/skills/ 58 skills listing + full + total tokens
156
- ~/.claude/agents/ 36 agents frontmatter name: resolution
157
- ~/.claude/agent_mobile/ ios-developer → agent_mobile/ios/AGENT.md
158
- ~/.claude/commands/ 65 commands relative path naming
159
- ~/.claude/workflows/ 10 workflows
160
- ~/.claude/CLAUDE.md + project CLAUDE.md chain
161
-
162
- OverheadReport:
163
- "56/64 skills unused in last 10 sessions → ~1033 listing tokens wasted"
164
- "CLAUDE.md chain is 3222 tokens"
131
+ skills, agents, commands, workflows, CLAUDE.md chain
132
+ 3-level token estimation: listing, full, total
133
+
134
+ ResourceTracker records actual usage per session
135
+ OverheadReport identifies unused resources + token waste
165
136
  ```
166
137
 
167
- ### Layer 7Observation Capture (new in v0.6)
138
+ > **Transparency note:** Claude Code loads ALL resources into its system prompt no external tool can prevent this. memory-hub provides **analysis and prioritization**, not filtering. To reduce token overhead, remove or relocate unused skills/agents based on the overhead report.
139
+
140
+ ### Layer 6 — Observation Capture
168
141
 
169
142
  ```
170
143
  Tool output contains "IMPORTANT: always pool DB connections"
171
144
  → observation entity (importance=4) saved to L2
172
- → included in session summary
173
- → searchable across sessions
174
145
 
175
146
  User prompt contains "remember that we use TypeScript strict"
176
147
  → observation entity (importance=3) saved to L2
148
+
149
+ 14 heuristic patterns: IMPORTANT, CRITICAL, SECURITY, DEPRECATED,
150
+ decision:, discovered, root cause, switched to, TODO:, FIXME:,
151
+ HACK:, performance:, bottleneck, OOM, don't, never, prefer, etc.
177
152
  ```
178
153
 
179
154
  ---
@@ -187,7 +162,7 @@ User prompt contains "remember that we use TypeScript strict"
187
162
  │ 5 Lifecycle Hooks │
188
163
  │ ┌───────────────┐ ┌──────────────┐ ┌──────────────┐ │
189
164
  │ │ PostToolUse │ │ PreCompact │ │ PostCompact │ │
190
- │ │ entity capture│ │ inject │ │ save summary │ │
165
+ │ │ batch queue │ │ inject │ │ save summary │ │
191
166
  │ └──────┬────────┘ │ priorities │ └──────┬───────┘ │
192
167
  │ │ └──────┬───────┘ │ │
193
168
  │ ┌──────┴───────┐ │ ┌──────┴───────┐ │
@@ -196,43 +171,32 @@ User prompt contains "remember that we use TypeScript strict"
196
171
  │ │past context │ │ │ summarize │ │
197
172
  │ └──────────────┘ │ └──────────────┘ │
198
173
  │ │ │
199
- │ MCP Server (stdio) Health Monitor │
200
- ┌─────────────────────┐ ┌────────────────────────┐ │
201
- │ │ memory_recall │ │ sqlite, fts5, disk,
202
- │ │ memory_entities │ │ │ integrity checks │
203
- │ │ memory_session_notes └────────────────────────┘
204
- │ │ memory_store
205
- │ │ memory_context_budget Smart Resource Loader │
206
- │ │ memory_search ←L1 ┌────────────────────────┐ │
207
- │ │ memory_timeline ←L2 │ │ │ track usage → predict
208
- │ │ memory_fetch ←L3 │ │ │ → budget → recommend │ │
209
- memory_health │ │ └────────────────────────┘ │
210
- └─────────────────────┘
211
- Browser UI (:37888) │
212
- ┌────────────────────────┐
213
- search, browse, stats
214
- └────────────────────────┘ │
215
- │ │ │
216
- └────────────────────────────┼────────────────────────────────┘
174
+ │ MCP Server (stdio, long-lived)
175
+ ┌─────────────────────────────────────────────────────┐
176
+ │ │ memory_recall memory_search (L1 index) │ │
177
+ │ │ memory_entities memory_timeline (L2 context)
178
+ │ │ memory_session_notes memory_fetch (L3 full)
179
+ │ │ memory_store memory_context_budget
180
+ │ │ memory_health
181
+ │ │
182
+ │ │ L1 WorkingMemory: read-through cache over L2
183
+ └─────────────────────────────────────────────────────┘
184
+
185
+ Resource Intelligence Browser UI (:37888)
186
+ ┌──────────────────┐ ┌──────────────────┐
187
+ scan → track → │ search, browse, │ │
188
+ analyze overhead stats, health
189
+ └──────────────────┘ └──────────────────┘
190
+ └──────────────────────────────────────────────────────────────┘
217
191
 
218
192
  ┌─────────┴──────────┐
219
193
  │ SQLite + FTS5 │
220
194
  │ ~/.claude- │
221
195
  │ memory-hub/ │
222
- │ memory.db │
223
196
  │ │
224
- sessions
225
- entities
226
- session_notes
227
- │ long_term_ │
228
- │ summaries │
229
- │ resource_usage │
230
- │ fts_memories │
231
- │ tfidf_index │
232
- │ embeddings │
233
- │ claude_md_ │
234
- │ registry │
235
- │ health_checks │
197
+ memory.db
198
+ batch/queue.jsonl
199
+ logs/
236
200
  └────────────────────┘
237
201
  ```
238
202
 
@@ -242,19 +206,20 @@ User prompt contains "remember that we use TypeScript strict"
242
206
 
243
207
  ```
244
208
  ┌─────────────────────────────────────────────────────┐
245
- │ L1: WorkingMemory in-process Map
246
- Current session only <1ms access
247
- Lives in MCP server FIFO 50 entries/session
209
+ │ L1: WorkingMemory Read-through cache
210
+ Lives in MCP server <1ms (cache hit)
211
+ Backed by SessionStore Auto-refresh on miss
212
+ │ TTL: 5 minutes Max 50 entries/session │
248
213
  ├─────────────────────────────────────────────────────┤
249
214
  │ L2: SessionStore SQLite │
250
215
  │ Entities + notes <10ms access │
251
- files_read, file_modified Per-session scope │
252
- errors, decisions Importance scored
216
+ files, errors, decisions Per-session scope │
217
+ observations (14 patterns) Importance scored 1-5
253
218
  ├─────────────────────────────────────────────────────┤
254
219
  │ L3: LongTermStore SQLite + FTS5 + TF-IDF │
255
220
  │ Cross-session summaries <100ms access │
256
221
  │ Hybrid ranked search Persistent forever │
257
- Auto-injected on start 3-layer progressive │
222
+ Semantic embeddings 3-layer progressive │
258
223
  └─────────────────────────────────────────────────────┘
259
224
  ```
260
225
 
@@ -270,7 +235,7 @@ bunx claude-memory-hub install
270
235
 
271
236
  One command. Registers MCP server + 5 hooks globally. Works on CLI, VS Code, JetBrains.
272
237
 
273
- **Coming from claude-mem?** The installer auto-detects `~/.claude-mem/claude-mem.db` and migrates your data automatically. No manual steps needed.
238
+ **Coming from claude-mem?** The installer auto-detects `~/.claude-mem/claude-mem.db` and migrates your data automatically.
274
239
 
275
240
  ### Update
276
241
 
@@ -278,24 +243,8 @@ One command. Registers MCP server + 5 hooks globally. Works on CLI, VS Code, Jet
278
243
  bunx claude-memory-hub@latest install
279
244
  ```
280
245
 
281
- Or if installed globally:
282
-
283
- ```bash
284
- bun install -g claude-memory-hub@latest
285
- claude-memory-hub install
286
- ```
287
-
288
246
  Your data at `~/.claude-memory-hub/` is preserved across updates. Schema migrations run automatically.
289
247
 
290
- ### From source
291
-
292
- ```bash
293
- git clone https://github.com/TranHoaiHung/claude-memory-hub.git ~/.claude-memory-hub
294
- cd ~/.claude-memory-hub
295
- bun install && bun run build:all
296
- bunx . install
297
- ```
298
-
299
248
  ### All CLI commands
300
249
 
301
250
  ```bash
@@ -305,10 +254,10 @@ bunx claude-memory-hub status # Check installation
305
254
  bunx claude-memory-hub migrate # Import data from claude-mem
306
255
  bunx claude-memory-hub viewer # Open browser UI at localhost:37888
307
256
  bunx claude-memory-hub health # Run health diagnostics
308
- bunx claude-memory-hub reindex # Rebuild TF-IDF search index
257
+ bunx claude-memory-hub reindex # Rebuild TF-IDF + embedding indexes
309
258
  bunx claude-memory-hub export # Export data as JSONL to stdout
310
- bunx claude-memory-hub import # Import JSONL from stdin
311
- bunx claude-memory-hub cleanup # Remove old data (default: 90 days)
259
+ bunx claude-memory-hub import # Import JSONL from stdin (--dry-run)
260
+ bunx claude-memory-hub cleanup # Remove old data (--days N, default 90)
312
261
  ```
313
262
 
314
263
  ### Requirements
@@ -327,13 +276,13 @@ Claude can call these tools directly during conversation:
327
276
 
328
277
  | Tool | What it does | When to use |
329
278
  |------|-------------|-------------|
330
- | `memory_recall` | FTS5 search past session summaries | Starting a task, looking for prior work |
279
+ | `memory_recall` | FTS5 search past sessions (supports `max_tokens`) | Starting a task, looking for prior work |
331
280
  | `memory_entities` | Find all sessions that touched a file | Before editing a file, understanding history |
332
- | `memory_session_notes` | Current session activity summary | Mid-session, checking what's been done |
281
+ | `memory_session_notes` | Current session activity (L1 cache) | Mid-session, checking what's been done |
333
282
  | `memory_store` | Manually save a note or decision | Preserving important context |
334
- | `memory_context_budget` | Analyze token costs + recommendations | Optimizing which resources to load |
283
+ | `memory_context_budget` | Analyze token costs + overhead report | Understanding resource usage |
335
284
 
336
- ### 3-Layer Search (new in v0.5)
285
+ ### 3-Layer Search
337
286
 
338
287
  | Tool | Layer | Tokens/result | When to use |
339
288
  |------|-------|---------------|-------------|
@@ -345,7 +294,46 @@ Claude can call these tools directly during conversation:
345
294
 
346
295
  | Tool | What it does |
347
296
  |------|-------------|
348
- | `memory_health` | Check database, FTS5, disk, integrity status |
297
+ | `memory_health` | Check database, FTS5, disk, embeddings, integrity status |
298
+
299
+ ---
300
+
301
+ ## Data Export/Import
302
+
303
+ ### Export
304
+
305
+ ```bash
306
+ # Full export
307
+ bunx claude-memory-hub export > backup.jsonl
308
+
309
+ # Incremental (since timestamp)
310
+ bunx claude-memory-hub export --since 1743580800000 > incremental.jsonl
311
+
312
+ # Single table
313
+ bunx claude-memory-hub export --table sessions > sessions.jsonl
314
+ ```
315
+
316
+ ### Import
317
+
318
+ ```bash
319
+ # Import from file
320
+ bunx claude-memory-hub import < backup.jsonl
321
+
322
+ # Validate without writing
323
+ bunx claude-memory-hub import --dry-run < backup.jsonl
324
+ ```
325
+
326
+ ### Cleanup
327
+
328
+ ```bash
329
+ # Remove data older than 90 days (default)
330
+ bunx claude-memory-hub cleanup
331
+
332
+ # Custom retention
333
+ bunx claude-memory-hub cleanup --days 30
334
+ ```
335
+
336
+ Format: JSONL (one JSON object per line). Embedding BLOBs encoded as base64. Import uses UPSERT — safe to re-run.
349
337
 
350
338
  ---
351
339
 
@@ -366,20 +354,14 @@ Opens a dark-themed dashboard at `http://localhost:37888` with:
366
354
 
367
355
  ## Migrating from claude-mem
368
356
 
369
- If you're already using [claude-mem](https://github.com/nicobailey-llc/claude-mem), migration is seamless:
370
-
371
357
  ```bash
372
358
  # Automatic (during install)
373
359
  bunx claude-memory-hub install
374
- # → Detects ~/.claude-mem/claude-mem.db automatically
375
- # → Migrates sessions, observations, summaries
376
360
 
377
361
  # Manual
378
362
  bunx claude-memory-hub migrate
379
363
  ```
380
364
 
381
- ### What gets migrated
382
-
383
365
  | claude-mem | → | memory-hub |
384
366
  |------------|---|------------|
385
367
  | `sdk_sessions` | → | `sessions` |
@@ -397,13 +379,14 @@ Migration is idempotent — safe to run multiple times with zero duplicates.
397
379
  | Version | What it solved |
398
380
  |---------|---------------|
399
381
  | **v0.1.0** | Cross-session memory, entity tracking, FTS5 search |
400
- | **v0.2.0** | Compact interceptor (PreCompact/PostCompact hooks), context enrichment, importance scoring |
382
+ | **v0.2.0** | Compact interceptor (PreCompact/PostCompact), context enrichment, importance scoring |
401
383
  | **v0.3.0** | Removed API key requirement, 1-command install |
402
- | **v0.4.0** | Smart resource loading, token budget optimization |
384
+ | **v0.4.0** | Resource usage tracking, token overhead analysis |
403
385
  | **v0.5.0** | Production hardening, hybrid search, 3-layer progressive search, browser UI, health monitoring, claude-mem migration |
404
- | **v0.6.0** | ResourceRegistry (170 resources), semantic search (384-dim embeddings), observation capture, CLAUDE.md tracking, 3-tier LLM summarization, overhead analysis |
386
+ | **v0.6.0** | ResourceRegistry (170 resources), semantic search (384-dim embeddings), observation capture, CLAUDE.md tracking, 3-tier LLM summarization |
405
387
  | **v0.7.0** | Honest resource analysis, semantic search scaling, batch embeddings, 14 observation patterns, DB auto-cleanup, summarizer retry |
406
- | **v0.8.0** | 91 unit tests (was 0%), L1 WorkingMemory read-through cache, PostToolUse batch queue (75ms→3ms), JSONL export/import CLI, data cleanup command |
388
+ | **v0.8.0** | 91 unit tests (was 0%), L1 read-through cache, PostToolUse batch queue (75ms→3ms), JSONL export/import, data cleanup CLI, CI/CD auto-publish |
389
+ | **v0.8.1** | Token-budget-aware MCP tools (`max_tokens`), proactive mid-session memory retrieval (topic-shift detection), session-end batch flush |
407
390
 
408
391
  See [CHANGELOG.md](CHANGELOG.md) for full details.
409
392
 
@@ -437,7 +420,11 @@ All data stored locally at `~/.claude-memory-hub/`.
437
420
 
438
421
  ```
439
422
  ~/.claude-memory-hub/
440
- ├── memory.db # SQLite database (sessions, entities, summaries)
423
+ ├── memory.db # SQLite database (all memory data)
424
+ ├── batch/
425
+ │ └── queue.jsonl # PostToolUse batch queue (auto-flushed)
426
+ ├── proactive/
427
+ │ └── <session>.json # Topic tracking state (auto-cleaned)
441
428
  └── logs/
442
429
  └── memory-hub.log # Structured JSON logs (auto-rotated at 5MB)
443
430
  ```
@@ -1833,6 +1833,132 @@ function isBatchEnabled() {
1833
1833
  return mode !== "disabled";
1834
1834
  }
1835
1835
 
1836
+ // src/retrieval/proactive-retrieval.ts
1837
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync4 } from "fs";
1838
+ import { join as join6 } from "path";
1839
+ import { homedir as homedir5 } from "os";
1840
+ var log6 = createLogger("proactive-retrieval");
1841
+ var DATA_DIR2 = join6(homedir5(), ".claude-memory-hub");
1842
+ var PROACTIVE_DIR = join6(DATA_DIR2, "proactive");
1843
+ var TOOL_CALL_INTERVAL = 15;
1844
+ var MAX_INJECTION_CHARS = 1500;
1845
+ function evaluateProactiveInjection(sessionId, toolName, toolInput, toolResponse) {
1846
+ const state = loadState(sessionId);
1847
+ state.toolCallCount++;
1848
+ const filePath = extractFilePath(toolName, toolInput);
1849
+ if (filePath) {
1850
+ state.recentFiles = [...new Set([filePath, ...state.recentFiles])].slice(0, 20);
1851
+ }
1852
+ const shouldTrigger = state.toolCallCount % TOOL_CALL_INTERVAL === 0 || toolName === "Bash" && typeof toolResponse.exit_code === "number" && toolResponse.exit_code !== 0 && state.toolCallCount > 5;
1853
+ if (!shouldTrigger) {
1854
+ saveState(sessionId, state);
1855
+ return { shouldInject: false };
1856
+ }
1857
+ const currentTopic = detectTopic(state.recentFiles);
1858
+ if (!currentTopic || state.injectedTopics.includes(currentTopic)) {
1859
+ saveState(sessionId, state);
1860
+ return { shouldInject: false };
1861
+ }
1862
+ const ltStore = new LongTermStore;
1863
+ const results = ltStore.search(currentTopic, 2);
1864
+ if (results.length === 0) {
1865
+ state.injectedTopics.push(currentTopic);
1866
+ saveState(sessionId, state);
1867
+ return { shouldInject: false };
1868
+ }
1869
+ const lines = [`**Relevant past context** (topic: ${currentTopic}):`];
1870
+ for (const r of results) {
1871
+ const date = new Date(r.created_at).toLocaleDateString();
1872
+ lines.push(`- [${date}] ${r.summary.slice(0, 200)}`);
1873
+ const files = safeJson4(r.files_touched, []);
1874
+ if (files.length > 0)
1875
+ lines.push(` Files: ${files.slice(0, 3).join(", ")}`);
1876
+ }
1877
+ let context = lines.join(`
1878
+ `);
1879
+ if (context.length > MAX_INJECTION_CHARS) {
1880
+ context = context.slice(0, MAX_INJECTION_CHARS) + `
1881
+ [...truncated]`;
1882
+ }
1883
+ state.injectedTopics.push(currentTopic);
1884
+ state.lastInjectionAt = Date.now();
1885
+ saveState(sessionId, state);
1886
+ log6.info("proactive injection triggered", { sessionId, topic: currentTopic, results: results.length });
1887
+ return { shouldInject: true, additionalContext: context };
1888
+ }
1889
+ function cleanupProactiveState(sessionId) {
1890
+ const path = statePath(sessionId);
1891
+ try {
1892
+ if (existsSync6(path)) {
1893
+ const { unlinkSync: unlinkSync2 } = __require("fs");
1894
+ unlinkSync2(path);
1895
+ }
1896
+ } catch {}
1897
+ }
1898
+ function detectTopic(recentFiles) {
1899
+ if (recentFiles.length < 3)
1900
+ return null;
1901
+ const dirs = recentFiles.map((f) => f.split("/").slice(0, -1).join("/")).filter(Boolean);
1902
+ const dirCounts = new Map;
1903
+ for (const d of dirs) {
1904
+ const parts = d.split("/").filter(Boolean);
1905
+ const leaf = parts[parts.length - 1];
1906
+ if (leaf && leaf !== "src" && leaf !== "lib" && leaf !== "utils") {
1907
+ dirCounts.set(leaf, (dirCounts.get(leaf) ?? 0) + 1);
1908
+ }
1909
+ }
1910
+ let bestTopic = null;
1911
+ let bestCount = 0;
1912
+ for (const [topic, count] of dirCounts) {
1913
+ if (count > bestCount) {
1914
+ bestTopic = topic;
1915
+ bestCount = count;
1916
+ }
1917
+ }
1918
+ const fileNames = recentFiles.map((f) => f.split("/").pop() ?? "").filter(Boolean);
1919
+ const keywords = ["auth", "payment", "user", "api", "database", "config", "test", "migration", "deploy", "search"];
1920
+ for (const kw of keywords) {
1921
+ const matches = fileNames.filter((f) => f.toLowerCase().includes(kw));
1922
+ if (matches.length >= 2)
1923
+ return kw;
1924
+ }
1925
+ return bestTopic;
1926
+ }
1927
+ function statePath(sessionId) {
1928
+ return join6(PROACTIVE_DIR, `${sessionId.replace(/[^a-zA-Z0-9_-]/g, "_")}.json`);
1929
+ }
1930
+ function loadState(sessionId) {
1931
+ const path = statePath(sessionId);
1932
+ try {
1933
+ if (existsSync6(path)) {
1934
+ return JSON.parse(readFileSync4(path, "utf-8"));
1935
+ }
1936
+ } catch {}
1937
+ return { toolCallCount: 0, lastInjectionAt: 0, injectedTopics: [], recentFiles: [] };
1938
+ }
1939
+ function saveState(sessionId, state) {
1940
+ try {
1941
+ if (!existsSync6(PROACTIVE_DIR)) {
1942
+ mkdirSync4(PROACTIVE_DIR, { recursive: true, mode: 448 });
1943
+ }
1944
+ writeFileSync2(statePath(sessionId), JSON.stringify(state), "utf-8");
1945
+ } catch {}
1946
+ }
1947
+ function extractFilePath(toolName, toolInput) {
1948
+ if (toolName === "Read" || toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit") {
1949
+ const fp = toolInput.file_path;
1950
+ return typeof fp === "string" ? fp : undefined;
1951
+ }
1952
+ return;
1953
+ }
1954
+ function safeJson4(text, fallback) {
1955
+ try {
1956
+ return JSON.parse(text);
1957
+ } catch {
1958
+ return fallback;
1959
+ }
1960
+ }
1961
+
1836
1962
  // src/hooks-entry/post-tool-use.ts
1837
1963
  async function main() {
1838
1964
  if (process.env["CLAUDE_MEMORY_HUB_SKIP_HOOKS"] === "1")
@@ -1871,9 +1997,18 @@ async function main() {
1871
1997
  timestamp: Date.now()
1872
1998
  });
1873
1999
  tryFlush();
1874
- return;
1875
- } catch {}
2000
+ } catch {
2001
+ await handlePostToolUse(hook, project);
2002
+ }
2003
+ } else {
2004
+ await handlePostToolUse(hook, project);
1876
2005
  }
1877
- await handlePostToolUse(hook, project);
2006
+ try {
2007
+ const result = evaluateProactiveInjection(hook.session_id, hook.tool_name, hook.tool_input ?? {}, hook.tool_response ?? {});
2008
+ if (result.shouldInject && result.additionalContext) {
2009
+ process.stdout.write(JSON.stringify({ additionalContext: result.additionalContext }) + `
2010
+ `);
2011
+ }
2012
+ } catch {}
1878
2013
  }
1879
2014
  main().catch(() => {}).finally(() => process.exit(0));
@@ -2021,6 +2021,250 @@ async function indexEmbedding(docType, docId, text, db) {
2021
2021
  created_at = excluded.created_at`, [docType, docId, blob, Date.now()]);
2022
2022
  }
2023
2023
 
2024
+ // src/retrieval/proactive-retrieval.ts
2025
+ import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync, mkdirSync as mkdirSync3 } from "fs";
2026
+ import { join as join5 } from "path";
2027
+ import { homedir as homedir4 } from "os";
2028
+ var log9 = createLogger("proactive-retrieval");
2029
+ var DATA_DIR = join5(homedir4(), ".claude-memory-hub");
2030
+ var PROACTIVE_DIR = join5(DATA_DIR, "proactive");
2031
+ var TOOL_CALL_INTERVAL = 15;
2032
+ var MAX_INJECTION_CHARS = 1500;
2033
+ function evaluateProactiveInjection(sessionId, toolName, toolInput, toolResponse) {
2034
+ const state = loadState(sessionId);
2035
+ state.toolCallCount++;
2036
+ const filePath = extractFilePath(toolName, toolInput);
2037
+ if (filePath) {
2038
+ state.recentFiles = [...new Set([filePath, ...state.recentFiles])].slice(0, 20);
2039
+ }
2040
+ const shouldTrigger = state.toolCallCount % TOOL_CALL_INTERVAL === 0 || toolName === "Bash" && typeof toolResponse.exit_code === "number" && toolResponse.exit_code !== 0 && state.toolCallCount > 5;
2041
+ if (!shouldTrigger) {
2042
+ saveState(sessionId, state);
2043
+ return { shouldInject: false };
2044
+ }
2045
+ const currentTopic = detectTopic(state.recentFiles);
2046
+ if (!currentTopic || state.injectedTopics.includes(currentTopic)) {
2047
+ saveState(sessionId, state);
2048
+ return { shouldInject: false };
2049
+ }
2050
+ const ltStore = new LongTermStore;
2051
+ const results = ltStore.search(currentTopic, 2);
2052
+ if (results.length === 0) {
2053
+ state.injectedTopics.push(currentTopic);
2054
+ saveState(sessionId, state);
2055
+ return { shouldInject: false };
2056
+ }
2057
+ const lines = [`**Relevant past context** (topic: ${currentTopic}):`];
2058
+ for (const r of results) {
2059
+ const date = new Date(r.created_at).toLocaleDateString();
2060
+ lines.push(`- [${date}] ${r.summary.slice(0, 200)}`);
2061
+ const files = safeJson4(r.files_touched, []);
2062
+ if (files.length > 0)
2063
+ lines.push(` Files: ${files.slice(0, 3).join(", ")}`);
2064
+ }
2065
+ let context = lines.join(`
2066
+ `);
2067
+ if (context.length > MAX_INJECTION_CHARS) {
2068
+ context = context.slice(0, MAX_INJECTION_CHARS) + `
2069
+ [...truncated]`;
2070
+ }
2071
+ state.injectedTopics.push(currentTopic);
2072
+ state.lastInjectionAt = Date.now();
2073
+ saveState(sessionId, state);
2074
+ log9.info("proactive injection triggered", { sessionId, topic: currentTopic, results: results.length });
2075
+ return { shouldInject: true, additionalContext: context };
2076
+ }
2077
+ function cleanupProactiveState(sessionId) {
2078
+ const path = statePath(sessionId);
2079
+ try {
2080
+ if (existsSync5(path)) {
2081
+ const { unlinkSync } = __require("fs");
2082
+ unlinkSync(path);
2083
+ }
2084
+ } catch {}
2085
+ }
2086
+ function detectTopic(recentFiles) {
2087
+ if (recentFiles.length < 3)
2088
+ return null;
2089
+ const dirs = recentFiles.map((f) => f.split("/").slice(0, -1).join("/")).filter(Boolean);
2090
+ const dirCounts = new Map;
2091
+ for (const d of dirs) {
2092
+ const parts = d.split("/").filter(Boolean);
2093
+ const leaf = parts[parts.length - 1];
2094
+ if (leaf && leaf !== "src" && leaf !== "lib" && leaf !== "utils") {
2095
+ dirCounts.set(leaf, (dirCounts.get(leaf) ?? 0) + 1);
2096
+ }
2097
+ }
2098
+ let bestTopic = null;
2099
+ let bestCount = 0;
2100
+ for (const [topic, count] of dirCounts) {
2101
+ if (count > bestCount) {
2102
+ bestTopic = topic;
2103
+ bestCount = count;
2104
+ }
2105
+ }
2106
+ const fileNames = recentFiles.map((f) => f.split("/").pop() ?? "").filter(Boolean);
2107
+ const keywords = ["auth", "payment", "user", "api", "database", "config", "test", "migration", "deploy", "search"];
2108
+ for (const kw of keywords) {
2109
+ const matches = fileNames.filter((f) => f.toLowerCase().includes(kw));
2110
+ if (matches.length >= 2)
2111
+ return kw;
2112
+ }
2113
+ return bestTopic;
2114
+ }
2115
+ function statePath(sessionId) {
2116
+ return join5(PROACTIVE_DIR, `${sessionId.replace(/[^a-zA-Z0-9_-]/g, "_")}.json`);
2117
+ }
2118
+ function loadState(sessionId) {
2119
+ const path = statePath(sessionId);
2120
+ try {
2121
+ if (existsSync5(path)) {
2122
+ return JSON.parse(readFileSync3(path, "utf-8"));
2123
+ }
2124
+ } catch {}
2125
+ return { toolCallCount: 0, lastInjectionAt: 0, injectedTopics: [], recentFiles: [] };
2126
+ }
2127
+ function saveState(sessionId, state) {
2128
+ try {
2129
+ if (!existsSync5(PROACTIVE_DIR)) {
2130
+ mkdirSync3(PROACTIVE_DIR, { recursive: true, mode: 448 });
2131
+ }
2132
+ writeFileSync(statePath(sessionId), JSON.stringify(state), "utf-8");
2133
+ } catch {}
2134
+ }
2135
+ function extractFilePath(toolName, toolInput) {
2136
+ if (toolName === "Read" || toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit") {
2137
+ const fp = toolInput.file_path;
2138
+ return typeof fp === "string" ? fp : undefined;
2139
+ }
2140
+ return;
2141
+ }
2142
+ function safeJson4(text, fallback) {
2143
+ try {
2144
+ return JSON.parse(text);
2145
+ } catch {
2146
+ return fallback;
2147
+ }
2148
+ }
2149
+
2150
+ // src/capture/batch-queue.ts
2151
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2, appendFileSync as appendFileSync2, unlinkSync, statSync as statSync2 } from "fs";
2152
+ import { join as join6 } from "path";
2153
+ import { homedir as homedir5 } from "os";
2154
+ var log10 = createLogger("batch-queue");
2155
+ var DATA_DIR2 = join6(homedir5(), ".claude-memory-hub");
2156
+ var BATCH_DIR = join6(DATA_DIR2, "batch");
2157
+ var QUEUE_PATH = join6(BATCH_DIR, "queue.jsonl");
2158
+ var LOCK_PATH = join6(BATCH_DIR, "queue.lock");
2159
+ var MAX_QUEUE_SIZE = 100 * 1024;
2160
+ var LOCK_STALE_MS = 30000;
2161
+ function enqueueEvent(event) {
2162
+ try {
2163
+ ensureBatchDir();
2164
+ const line = JSON.stringify(event) + `
2165
+ `;
2166
+ appendFileSync2(QUEUE_PATH, line, "utf-8");
2167
+ } catch (err) {
2168
+ log10.error("enqueue failed", { error: String(err) });
2169
+ throw err;
2170
+ }
2171
+ }
2172
+ function tryFlush() {
2173
+ try {
2174
+ if (!existsSync6(QUEUE_PATH))
2175
+ return false;
2176
+ const stat = statSync2(QUEUE_PATH);
2177
+ if (stat.size === 0)
2178
+ return false;
2179
+ if (!tryAcquireLock())
2180
+ return false;
2181
+ try {
2182
+ flushQueue();
2183
+ return true;
2184
+ } finally {
2185
+ releaseLock();
2186
+ }
2187
+ } catch (err) {
2188
+ log10.error("flush failed", { error: String(err) });
2189
+ return false;
2190
+ }
2191
+ }
2192
+ function flushQueue() {
2193
+ const content = readFileSync4(QUEUE_PATH, "utf-8").trim();
2194
+ if (!content)
2195
+ return;
2196
+ const events = [];
2197
+ for (const line of content.split(`
2198
+ `)) {
2199
+ try {
2200
+ events.push(JSON.parse(line));
2201
+ } catch {
2202
+ log10.warn("skipping malformed queue line");
2203
+ }
2204
+ }
2205
+ if (events.length === 0)
2206
+ return;
2207
+ const store = new SessionStore;
2208
+ const tracker = new ResourceTracker;
2209
+ const registry = getResourceRegistry();
2210
+ const db = store["db"];
2211
+ db.transaction(() => {
2212
+ for (const event of events) {
2213
+ store.upsertSession({
2214
+ id: event.session.id,
2215
+ project: event.session.project,
2216
+ started_at: event.session.started_at,
2217
+ status: "active"
2218
+ });
2219
+ for (const entity of event.entities) {
2220
+ store.insertEntity({ ...entity, project: event.session.project });
2221
+ }
2222
+ if (event.resources) {
2223
+ for (const r of event.resources) {
2224
+ const resource = registry.resolve(r.type, r.name);
2225
+ tracker.trackUsage(event.session.id, event.session.project, r.type, r.name, r.tokenCost ?? resource?.full_tokens ?? 0);
2226
+ }
2227
+ }
2228
+ }
2229
+ })();
2230
+ writeFileSync2(QUEUE_PATH, "", "utf-8");
2231
+ log10.info("batch flushed", { events: events.length });
2232
+ }
2233
+ function tryAcquireLock() {
2234
+ try {
2235
+ if (existsSync6(LOCK_PATH)) {
2236
+ const lockContent = readFileSync4(LOCK_PATH, "utf-8").trim();
2237
+ const [pidStr, timestampStr] = lockContent.split(":");
2238
+ const lockTime = Number(timestampStr);
2239
+ if (Date.now() - lockTime < LOCK_STALE_MS) {
2240
+ const pid = Number(pidStr);
2241
+ try {
2242
+ process.kill(pid, 0);
2243
+ return false;
2244
+ } catch {}
2245
+ }
2246
+ }
2247
+ writeFileSync2(LOCK_PATH, `${process.pid}:${Date.now()}`, "utf-8");
2248
+ return true;
2249
+ } catch {
2250
+ return false;
2251
+ }
2252
+ }
2253
+ function releaseLock() {
2254
+ try {
2255
+ unlinkSync(LOCK_PATH);
2256
+ } catch {}
2257
+ }
2258
+ function ensureBatchDir() {
2259
+ if (!existsSync6(BATCH_DIR)) {
2260
+ mkdirSync4(BATCH_DIR, { recursive: true, mode: 448 });
2261
+ }
2262
+ }
2263
+ function isBatchEnabled() {
2264
+ const mode = process.env["CLAUDE_MEMORY_HUB_BATCH"] ?? "auto";
2265
+ return mode !== "disabled";
2266
+ }
2267
+
2024
2268
  // src/hooks-entry/session-end.ts
2025
2269
  async function main() {
2026
2270
  if (process.env["CLAUDE_MEMORY_HUB_SKIP_HOOKS"] === "1")
@@ -2036,6 +2280,10 @@ async function main() {
2036
2280
  }
2037
2281
  const project = projectFromCwd(hook.cwd ?? process.env["CLAUDE_CWD"] ?? process.cwd());
2038
2282
  await handleSessionEnd(hook, project);
2283
+ try {
2284
+ tryFlush();
2285
+ } catch {}
2286
+ cleanupProactiveState(hook.session_id);
2039
2287
  const store = new SessionStore;
2040
2288
  if (store.getSession(hook.session_id)) {
2041
2289
  await new SessionSummarizer().summarize(hook.session_id, project).catch(() => {});
package/dist/index.js CHANGED
@@ -15593,8 +15593,17 @@ function sanitizeFtsQuery2(query) {
15593
15593
  }
15594
15594
 
15595
15595
  // src/mcp/tool-handlers.ts
15596
+ function truncateToTokenBudget(text, maxTokens) {
15597
+ if (!maxTokens || maxTokens <= 0)
15598
+ return text;
15599
+ const maxChars = maxTokens * 4;
15600
+ if (text.length <= maxChars)
15601
+ return text;
15602
+ return text.slice(0, maxChars) + `
15603
+ [...truncated to fit ~` + maxTokens + " token budget]";
15604
+ }
15596
15605
  async function handleMemoryRecall(args) {
15597
- const { query, limit = 5 } = args;
15606
+ const { query, limit = 5, max_tokens } = args;
15598
15607
  if (!query?.trim())
15599
15608
  return "No query provided.";
15600
15609
  const builder = new ContextBuilder;
@@ -15604,9 +15613,10 @@ async function handleMemoryRecall(args) {
15604
15613
 
15605
15614
  This appears to be a new topic with no prior history.`;
15606
15615
  }
15607
- return `Found ${ctx.resultCount} relevant memory(ies) (~${ctx.tokenEstimate} tokens):
15616
+ const output = `Found ${ctx.resultCount} relevant memory(ies) (~${ctx.tokenEstimate} tokens):
15608
15617
 
15609
15618
  ${ctx.text}`;
15619
+ return truncateToTokenBudget(output, max_tokens);
15610
15620
  }
15611
15621
  async function handleMemoryEntities(args) {
15612
15622
  const { file_path } = args;
@@ -15648,11 +15658,11 @@ async function handleMemoryStore(args) {
15648
15658
  return `Note saved to session ${session_id}.`;
15649
15659
  }
15650
15660
  async function handleMemorySearch(args) {
15651
- const { query, limit = 20, offset = 0, project } = args;
15661
+ const { query, limit = 20, offset = 0, project, max_tokens } = args;
15652
15662
  if (!query?.trim())
15653
15663
  return "No query provided.";
15654
15664
  const results = await searchIndex(query, { limit: Math.min(limit, 50), offset, ...project ? { project } : {} });
15655
- return formatSearchIndex(results);
15665
+ return truncateToTokenBudget(formatSearchIndex(results), max_tokens);
15656
15666
  }
15657
15667
  async function handleMemoryTimeline(args) {
15658
15668
  const { id, type, depth = 3 } = args;
@@ -15662,11 +15672,11 @@ async function handleMemoryTimeline(args) {
15662
15672
  return formatTimeline(entries);
15663
15673
  }
15664
15674
  async function handleMemoryFetch(args) {
15665
- const { ids } = args;
15675
+ const { ids, max_tokens } = args;
15666
15676
  if (!ids?.length)
15667
15677
  return "No IDs provided.";
15668
15678
  const records = fetchFullRecords(ids.slice(0, 20));
15669
- return formatFullRecords(records);
15679
+ return truncateToTokenBudget(formatFullRecords(records), max_tokens);
15670
15680
  }
15671
15681
  async function handleMemoryHealth() {
15672
15682
  const report = runHealthCheck();
@@ -15741,7 +15751,8 @@ var TOOL_DEFINITIONS = [
15741
15751
  type: "object",
15742
15752
  properties: {
15743
15753
  query: { type: "string", description: "Natural language search query" },
15744
- limit: { type: "number", description: "Max results (default 5, max 10)" }
15754
+ limit: { type: "number", description: "Max results (default 5, max 10)" },
15755
+ max_tokens: { type: "number", description: "Max output tokens. Results truncated to fit budget. Default: unlimited" }
15745
15756
  },
15746
15757
  required: ["query"]
15747
15758
  }
@@ -15800,7 +15811,8 @@ var TOOL_DEFINITIONS = [
15800
15811
  query: { type: "string", description: "Search query" },
15801
15812
  limit: { type: "number", description: "Max results (default 20)" },
15802
15813
  offset: { type: "number", description: "Pagination offset (default 0)" },
15803
- project: { type: "string", description: "Filter by project" }
15814
+ project: { type: "string", description: "Filter by project" },
15815
+ max_tokens: { type: "number", description: "Max output tokens. Results truncated to fit budget" }
15804
15816
  },
15805
15817
  required: ["query"]
15806
15818
  }
@@ -15835,7 +15847,8 @@ var TOOL_DEFINITIONS = [
15835
15847
  required: ["id", "type"]
15836
15848
  },
15837
15849
  description: "Array of {id, type} from search results"
15838
- }
15850
+ },
15851
+ max_tokens: { type: "number", description: "Max output tokens. Records truncated to fit budget" }
15839
15852
  },
15840
15853
  required: ["ids"]
15841
15854
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-memory-hub",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Persistent memory system for Claude Code. Zero API key. Zero Python. 5 hooks + MCP server + SQLite FTS5 + semantic search.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",