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.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "claude-mem-lite",
13
- "version": "2.12.3",
13
+ "version": "2.14.0",
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.3",
3
+ "version": "2.14.0",
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/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, PreToolUse, SessionStart, Stop, UserPromptSubmit) to record observations without manual effort
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
- - **Intelligent dispatch** -- 3-tier progressive dispatch system automatically recommends the right skill or agent for the current task, triggered on SessionStart, UserPromptSubmit, and PreToolUse
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
- - **Closed-loop feedback** -- Tracks whether recommendations were adopted and whether sessions succeeded, improving future dispatch quality
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
- - **Haiku circuit breaker** -- After 3 consecutive LLM failures, disables Haiku dispatch for 5 minutes to prevent cascading latency
91
- - **Negation-aware intent** -- Handles complex prompts like "don't test, just fix the bug" correctly excludes negated intents even in mixed English/Chinese input
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 7 tools (search, timeline, get, save, stats, delete, compress)
148
- 3. **Configure hooks** -- `PostToolUse`, `PreToolUse`, `SessionStart`, `Stop`, `UserPromptSubmit` lifecycle hooks
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`, `session_summaries_fts`, `user_prompts_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
- PreToolUse (before tool execution)
286
- -> Dispatch: recommend skill/agent based on current action context (Tier 0→1→2)
287
-
288
- UserPromptSubmit
289
- -> Capture user prompt text to user_prompts table
290
- -> Increment session prompt counter
291
- -> Handoff: detect continuation intent → inject previous session context
292
- -> Dispatch: recommend skill/agent based on user's actual prompt (Tier 0→1→2)
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
- ### Intelligent Dispatch
303
+ ### Resource Registry
304
304
 
305
- The dispatch system proactively recommends skills and agents during coding sessions via a 3-tier progressive architecture:
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
- Tier 0: Fast Filter (<1ms)
309
- -> Skip read-only tools (Read, Glob, Grep, LSP...)
310
- -> Skip simple Bash queries (ls, cat, git status...)
311
- -> Skip when Claude already chose a Skill or Task agent
312
- -> Skip MCP-internal tools
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
- **Dispatch triggers:**
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
- # Intelligent dispatch
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, dispatch). Accepts `haiku` or `sonnet`. | `haiku` |
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
- 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-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 ageDays = (now_ms - o.created_at_epoch) / 86400000;
88
- const recency = 1 / (1 + ageDays);
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 ageDays = (now_ms - s.created_at_epoch) / 86400000;
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 bm25(observations_fts, 10, 5, 5, 3, 3, 2)
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
- bm25(observations_fts, 10, 5, 5, 3, 3, 2) as relevance
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 bm25(observations_fts, 10, 5, 5, 3, 3, 2)
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
- bm25(observations_fts, 10, 5, 5, 3, 3, 2) as relevance
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 bm25(observations_fts, 10, 5, 5, 3, 3, 2)
63
+ ORDER BY ${OBS_BM25}
64
64
  LIMIT 5
65
65
  `).all(ftsQuery, project, cutoff);
66
66
  } catch (e) { debugCatch(e, 'crossProjectSearch'); }
@@ -62,7 +62,8 @@ export async function acquireLLMSlot() {
62
62
  active++;
63
63
  }
64
64
  } catch {
65
- active++;
65
+ // Corrupt/unreadable semaphore file — treat as stale and remove
66
+ try { unlinkSync(fp); } catch {}
66
67
  }
67
68
  }
68
69
  } catch {}
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