claude-session-continuity-mcp 1.12.0 → 1.13.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # claude-session-continuity-mcp (v1.10.0)
1
+ # claude-session-continuity-mcp (v1.12.1)
2
2
 
3
- > **Zero Re-explanation Session Continuity for Claude Code** — Automatic context capture + semantic search
3
+ > **Zero Re-explanation Session Continuity for Claude Code** — Automatic context capture + semantic search + auto error→solution pipeline
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/claude-session-continuity-mcp.svg)](https://www.npmjs.com/package/claude-session-continuity-mcp)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -27,7 +27,7 @@ Every new Claude Code session:
27
27
  ```bash
28
28
  # Session start → Auto-loads relevant context + recent session history
29
29
  # When asking → Auto-injects relevant memories/solutions
30
- # During conversation → Tracks active files (no noise memories)
30
+ # During conversation → Tracks active files + auto-injects error solutions
31
31
  # On compact → Structured handover context for continuity
32
32
  # On exit → Extracts commits, decisions, error-fix pairs from transcript
33
33
  ```
@@ -108,7 +108,7 @@ npm install claude-session-continuity-mcp
108
108
  |------|---------|----------|
109
109
  | `SessionStart` | `claude-hook-session-start` | Auto-loads project context on session start |
110
110
  | `UserPromptSubmit` | `claude-hook-user-prompt` | Auto-injects relevant memories + past reference search |
111
- | `PostToolUse` | `claude-hook-post-tool` | Tracks active files (Edit, Write) no noise memories |
111
+ | `PostToolUse` | `claude-hook-post-tool` | Tracks active files (Edit, Write) + auto-injects error solutions (Bash) |
112
112
  | `PreCompact` | `claude-hook-pre-compact` | Structured handover context before compression |
113
113
  | `Stop` | `claude-hook-session-end` | Extracts commits, decisions, error-fix pairs from transcript |
114
114
 
@@ -144,7 +144,10 @@ After installation, restart Claude Code to activate the hooks.
144
144
  | 📊 **Memory Classification** | 5 types: observation, decision, learning, error, pattern |
145
145
  | ✅ **Integrated Verification** | One-click build/test/lint execution |
146
146
  | 📋 **Task Management** | Priority-based task management |
147
- | 🔧 **Solution Archive** | Auto-search error solutions |
147
+ | 🔧 **Auto Error→Solution** | **(v1.12.0)** Bash errors auto-detect inject past solutions; session-end auto-records error-fix pairs |
148
+ | 💰 **Token Efficiency** | **(v1.11.0)** Removed loadContext from UserPromptSubmit (saves 24-60K tokens/session) |
149
+ | 📑 **Progressive Disclosure** | **(v1.11.0)** memory_search returns index first, memory_get for full content |
150
+ | ⏳ **Temporal Decay** | **(v1.11.0)** Memory scoring with type-specific half-lives for relevance |
148
151
  | 📝 **Structured Handover** | **(v1.10.0)** PreCompact saves work summary, active files, pending actions |
149
152
  | 🚪 **Smart Session End** | **(v1.10.0)** Extracts commits, decisions, error-fix pairs from transcript |
150
153
  | 🗑️ **Auto Noise Cleanup** | **(v1.10.0)** Auto-deletes stale observation memories (3d+) |
@@ -165,10 +168,12 @@ After installation, restart Claude Code to activate the hooks.
165
168
 
166
169
  **UserPromptSubmit Hook** (`npx claude-hook-user-prompt`):
167
170
  - Runs on every prompt submission
171
+ - **(v1.11.0)** No longer calls loadContext() — saves 24-60K tokens/session
168
172
  - Injects relevant context (filtered: decisions, learnings, errors only)
169
173
 
170
174
  **PostToolUse Hook** (`npx claude-hook-post-tool`):
171
175
  - Tracks hot file paths and updates `active_context.recent_files`
176
+ - **(v1.12.0)** Auto-detects Bash errors → searches solutions DB → injects past solutions into context
172
177
  - **No longer creates observation memories** (v1.10.0 — eliminates `[File Change]` noise)
173
178
 
174
179
  **PreCompact Hook** (`npx claude-hook-pre-compact`):
@@ -178,7 +183,9 @@ After installation, restart Claude Code to activate the hooks.
178
183
  **Stop Hook** (`npx claude-hook-session-end`):
179
184
  - Extracts commit messages from JSONL transcript (`git commit -m` patterns)
180
185
  - Extracts error-fix pairs (error → resolution within 3 messages)
186
+ - **(v1.12.0)** Auto-records error→fix pairs to solutions table for future reuse
181
187
  - Extracts decisions ("because", "instead of", "chose" patterns)
188
+ - **(v1.11.0)** Single-pass transcript parsing (4 JSONL reads → 1)
182
189
  - Stores structured metadata in `sessions.issues` column as JSON
183
190
 
184
191
  ### Example Output (Session Start)
@@ -280,7 +287,7 @@ Now we use `npm exec --`:
280
287
 
281
288
  ---
282
289
 
283
- ## Tools (v5 API) - 24 Focused Tools
290
+ ## Tools (v5 API) - 25 Focused Tools
284
291
 
285
292
  ### 1. Session Lifecycle (4) ⭐
286
293
 
@@ -363,7 +370,7 @@ verify_test({ project: "my-app" })
363
370
  verify_all({ project: "my-app" })
364
371
  ```
365
372
 
366
- ### 6. Memory System (4)
373
+ ### 6. Memory System (5)
367
374
 
368
375
  ```javascript
369
376
  // Store a classified memory
@@ -376,7 +383,7 @@ memory_store({
376
383
  relatedTo: 23 // Connect to existing memory
377
384
  })
378
385
 
379
- // Search memories (keyword or semantic)
386
+ // Search memories — returns index (id, type, tags, score) for token efficiency
380
387
  memory_search({
381
388
  query: "state management test",
382
389
  type: "learning",
@@ -384,6 +391,9 @@ memory_search({
384
391
  limit: 10
385
392
  })
386
393
 
394
+ // Get full memory content by ID (v1.11.0)
395
+ memory_get({ memoryId: 23 })
396
+
387
397
  // Find related memories (graph + semantic)
388
398
  memory_related({
389
399
  memoryId: 23,
@@ -528,6 +538,14 @@ npm run test:coverage
528
538
  - [x] Smart session-end: commit/decision/error-fix extraction from transcript (v1.10.0)
529
539
  - [x] Auto noise cleanup (3d+ observations, 14d+ auto-compact) (v1.10.0)
530
540
  - [x] 3 recent sessions display with structured metadata (v1.10.0)
541
+ - [x] Token efficiency — remove loadContext from UserPromptSubmit, saves 24-60K tokens/session (v1.11.0)
542
+ - [x] Single-pass transcript parsing, 4 JSONL reads → 1 (v1.11.0)
543
+ - [x] Temporal decay for memory scoring with type-specific half-lives (v1.11.0)
544
+ - [x] Progressive disclosure — memory_search returns index, memory_get for full content (v1.11.0)
545
+ - [x] Memory consolidation via Jaccard similarity (v1.11.0)
546
+ - [x] Auto error→solution pipeline — PostToolUse detects Bash errors, injects past solutions (v1.12.0)
547
+ - [x] SessionEnd auto-records error-fix pairs to solutions table (v1.12.0)
548
+ - [x] Cross-project solution search with current project prioritization (v1.12.0)
531
549
  - [ ] sqlite-vec native vector search (v2 - when data > 1000 records)
532
550
  - [ ] Web dashboard
533
551
  - [ ] Cloud sync option
@@ -567,6 +567,37 @@ async function main() {
567
567
  }
568
568
  catch { /* solutions table may not exist */ }
569
569
  }
570
+ // architecture_decisions 자동 누적 (세션에서 추출된 결정사항 병합)
571
+ if (decisions.length > 0) {
572
+ try {
573
+ const existing = db.prepare('SELECT architecture_decisions FROM project_context WHERE project = ?').get(project);
574
+ let existingDecisions = [];
575
+ if (existing?.architecture_decisions) {
576
+ try {
577
+ existingDecisions = JSON.parse(existing.architecture_decisions);
578
+ }
579
+ catch { /* ignore */ }
580
+ }
581
+ // 중복 제거 후 병합 (최대 20개 유지)
582
+ const merged = [...new Set([...existingDecisions, ...decisions])].slice(-20);
583
+ db.prepare(`
584
+ INSERT INTO project_context (project, architecture_decisions)
585
+ VALUES (?, ?)
586
+ ON CONFLICT(project) DO UPDATE SET architecture_decisions = ?
587
+ `).run(project, JSON.stringify(merged), JSON.stringify(merged));
588
+ }
589
+ catch { /* project_context table may not exist */ }
590
+ }
591
+ // 세션 임베딩 사전 생성 (search_sessions 성능 최적화)
592
+ try {
593
+ const lastSession = db.prepare('SELECT id FROM sessions WHERE project = ? ORDER BY timestamp DESC LIMIT 1').get(project);
594
+ if (lastSession && lastWork) {
595
+ // 간단한 임베딩은 동기적으로 시도하지 않고 DB에 표시만 남김
596
+ // MCP 서버의 generateEmbedding이 search 시 캐시 miss에서 lazy 생성
597
+ // session-end 훅은 transformers 모델 로드 오버헤드가 크므로 skip
598
+ }
599
+ }
600
+ catch { /* ignore */ }
570
601
  db.close();
571
602
  console.log(`[SessionEnd] Saved session for ${project}`);
572
603
  console.log(` Last work: ${lastWork.slice(0, 80)}`);
@@ -62,6 +62,11 @@ function cleanupNoiseMemories(db) {
62
62
  }
63
63
  catch { /* ignore */ }
64
64
  }
65
+ // 토큰 예산 시스템 (컨텍스트 무한 증가 방지)
66
+ const MAX_CONTEXT_TOKENS = parseInt(process.env.MCP_CONTEXT_BUDGET || '2000', 10);
67
+ function estimateTokens(text) {
68
+ return Math.ceil(text.length / 4); // 대략적 추정 (한글은 1.5~2배)
69
+ }
65
70
  function loadContext(dbPath, project) {
66
71
  if (!fs.existsSync(dbPath))
67
72
  return null;
@@ -70,15 +75,19 @@ function loadContext(dbPath, project) {
70
75
  // 노이즈 메모리 자동 정리
71
76
  cleanupNoiseMemories(db);
72
77
  const lines = [`# ${project} - Session Resumed\n`];
73
- // 현재 상태
78
+ let tokenBudget = MAX_CONTEXT_TOKENS;
79
+ // [Priority 1] 현재 상태
74
80
  const active = db.prepare('SELECT current_state, blockers FROM active_context WHERE project = ?').get(project);
75
81
  if (active?.current_state) {
76
- lines.push(`📍 **State**: ${active.current_state}`);
77
- if (active.blockers)
78
- lines.push(`🚧 **Blocker**: ${active.blockers}`);
79
- lines.push('');
82
+ const stateBlock = `📍 **State**: ${active.current_state}` + (active.blockers ? `\n🚧 **Blocker**: ${active.blockers}` : '');
83
+ const cost = estimateTokens(stateBlock);
84
+ if (tokenBudget > cost) {
85
+ lines.push(stateBlock);
86
+ lines.push('');
87
+ tokenBudget -= cost;
88
+ }
80
89
  }
81
- // 최근 3개 세션 (빈 세션 skip)
90
+ // [Priority 2] 최근 3개 세션 (빈 세션 skip)
82
91
  const recentSessions = db.prepare(`
83
92
  SELECT last_work, next_tasks, issues, timestamp FROM sessions
84
93
  WHERE project = ?
@@ -89,61 +98,83 @@ function loadContext(dbPath, project) {
89
98
  AND length(last_work) > 15
90
99
  ORDER BY timestamp DESC LIMIT 3
91
100
  `).all(project);
92
- if (recentSessions.length > 0) {
93
- lines.push('## Recent Sessions');
101
+ if (recentSessions.length > 0 && tokenBudget > 100) {
102
+ const sessionLines = ['## Recent Sessions'];
94
103
  for (const session of recentSessions) {
95
- // last_work 60자 제한 (토큰 예산)
96
104
  const work = session.last_work.length > 60 ? session.last_work.slice(0, 60) + '...' : session.last_work;
97
- lines.push(`- [${session.timestamp?.slice(0, 10) || '?'}] ${work}`);
98
- // 커밋 정보 (간결하게)
105
+ sessionLines.push(`- [${session.timestamp?.slice(0, 10) || '?'}] ${work}`);
99
106
  if (session.issues) {
100
107
  try {
101
108
  const meta = JSON.parse(session.issues);
102
109
  if (meta.commits?.length > 0) {
103
- lines.push(` commits: ${meta.commits.slice(0, 2).join('; ').slice(0, 80)}`);
110
+ sessionLines.push(` commits: ${meta.commits.slice(0, 2).join('; ').slice(0, 80)}`);
104
111
  }
105
112
  }
106
113
  catch { /* skip */ }
107
114
  }
108
115
  }
109
- lines.push('');
116
+ const cost = estimateTokens(sessionLines.join('\n'));
117
+ if (tokenBudget > cost) {
118
+ lines.push(...sessionLines, '');
119
+ tokenBudget -= cost;
120
+ }
110
121
  }
111
- // 사용자 지시사항
122
+ // [Priority 3] 사용자 지시사항 (가장 중요 - 예산 부족해도 high priority는 포함)
112
123
  try {
113
124
  const directives = db.prepare(`
114
125
  SELECT directive, priority FROM user_directives
115
126
  WHERE project = ? ORDER BY priority DESC, created_at DESC LIMIT 5
116
127
  `).all(project);
117
128
  if (directives.length > 0) {
118
- lines.push('## Directives');
129
+ const directiveLines = ['## Directives'];
119
130
  for (const d of directives) {
120
131
  const icon = d.priority === 'high' ? '🔴' : '📎';
121
- lines.push(`- ${icon} ${d.directive}`);
132
+ directiveLines.push(`- ${icon} ${d.directive}`);
133
+ }
134
+ const cost = estimateTokens(directiveLines.join('\n'));
135
+ // 지시사항은 예산 초과해도 high priority는 포함
136
+ const highOnly = directives.filter(d => d.priority === 'high');
137
+ if (tokenBudget > cost) {
138
+ lines.push(...directiveLines, '');
139
+ tokenBudget -= cost;
140
+ }
141
+ else if (highOnly.length > 0) {
142
+ const criticalLines = ['## Directives'];
143
+ for (const d of highOnly)
144
+ criticalLines.push(`- 🔴 ${d.directive}`);
145
+ lines.push(...criticalLines, '');
146
+ tokenBudget -= estimateTokens(criticalLines.join('\n'));
122
147
  }
123
- lines.push('');
124
148
  }
125
149
  }
126
150
  catch { /* table may not exist yet */ }
127
- // 미완료 태스크
128
- try {
129
- const tasks = db.prepare(`
130
- SELECT title, priority, status FROM tasks
131
- WHERE project = ? AND status IN ('pending', 'in_progress')
132
- ORDER BY priority DESC LIMIT 5
133
- `).all(project);
134
- if (tasks.length > 0) {
135
- lines.push('## Pending Tasks');
136
- for (const t of tasks) {
137
- const icon = t.status === 'in_progress' ? '🔄' : '⏳';
138
- lines.push(`- ${icon} [P${t.priority}] ${t.title}`);
151
+ // [Priority 4] 미완료 태스크
152
+ if (tokenBudget > 50) {
153
+ try {
154
+ const tasks = db.prepare(`
155
+ SELECT title, priority, status FROM tasks
156
+ WHERE project = ? AND status IN ('pending', 'in_progress')
157
+ ORDER BY priority DESC LIMIT 5
158
+ `).all(project);
159
+ if (tasks.length > 0) {
160
+ const taskLines = ['## Pending Tasks'];
161
+ for (const t of tasks) {
162
+ const icon = t.status === 'in_progress' ? '🔄' : '⏳';
163
+ taskLines.push(`- ${icon} [P${t.priority}] ${t.title}`);
164
+ }
165
+ const cost = estimateTokens(taskLines.join('\n'));
166
+ if (tokenBudget > cost) {
167
+ lines.push(...taskLines, '');
168
+ tokenBudget -= cost;
169
+ }
139
170
  }
140
- lines.push('');
141
171
  }
172
+ catch { /* table may not exist */ }
142
173
  }
143
- catch { /* table may not exist */ }
144
- // 중요 메모리 (temporal decay 적용)
145
- try {
146
- const memories = db.prepare(`
174
+ // [Priority 5] 중요 메모리 (temporal decay 적용, 예산 내에서)
175
+ if (tokenBudget > 80)
176
+ try {
177
+ const memories = db.prepare(`
147
178
  SELECT content, memory_type, importance, created_at, access_count FROM memories
148
179
  WHERE project = ?
149
180
  AND memory_type IN ('decision', 'learning', 'error', 'preference')
@@ -152,36 +183,43 @@ function loadContext(dbPath, project) {
152
183
  AND (tags NOT LIKE '%auto-compact%' OR tags IS NULL)
153
184
  ORDER BY importance DESC, accessed_at DESC LIMIT 20
154
185
  `).all(project);
155
- if (memories.length > 0) {
156
- // Decay 적용 후 top 5 선택
157
- const DECAY_RATES = {
158
- decision: 0.001, learning: 0.003, error: 0.01, preference: 0.002
159
- };
160
- const scored = memories.map(m => {
161
- const ageDays = (Date.now() - new Date(m.created_at).getTime()) / (1000 * 60 * 60 * 24);
162
- const decayRate = DECAY_RATES[m.memory_type] ?? 0.005;
163
- const score = m.importance * Math.exp(-decayRate * ageDays) * Math.log2(m.access_count + 2);
164
- return { ...m, score };
165
- }).sort((a, b) => b.score - a.score).slice(0, 5);
166
- const typeIcons = {
167
- decision: '🎯', learning: '📚', error: '⚠️', preference: '💡'
168
- };
169
- lines.push('## Key Memories');
170
- for (const m of scored) {
171
- const icon = typeIcons[m.memory_type] || '💭';
172
- const content = m.content.length > 80 ? m.content.slice(0, 80) + '...' : m.content;
173
- lines.push(`- ${icon} ${content}`);
186
+ if (memories.length > 0) {
187
+ // Decay 적용 후 top 5 선택
188
+ const DECAY_RATES = {
189
+ decision: 0.001, learning: 0.003, error: 0.01, preference: 0.002
190
+ };
191
+ const scored = memories.map(m => {
192
+ const ageDays = (Date.now() - new Date(m.created_at).getTime()) / (1000 * 60 * 60 * 24);
193
+ const decayRate = DECAY_RATES[m.memory_type] ?? 0.005;
194
+ const score = m.importance * Math.exp(-decayRate * ageDays) * Math.log2(m.access_count + 2);
195
+ return { ...m, score };
196
+ }).sort((a, b) => b.score - a.score).slice(0, 5);
197
+ const typeIcons = {
198
+ decision: '🎯', learning: '📚', error: '⚠️', preference: '💡'
199
+ };
200
+ const memoryLines = ['## Key Memories'];
201
+ for (const m of scored) {
202
+ const icon = typeIcons[m.memory_type] || '💭';
203
+ const content = m.content.length > 80 ? m.content.slice(0, 80) + '...' : m.content;
204
+ memoryLines.push(`- ${icon} ${content}`);
205
+ }
206
+ const cost = estimateTokens(memoryLines.join('\n'));
207
+ if (tokenBudget > cost) {
208
+ lines.push(...memoryLines, '');
209
+ tokenBudget -= cost;
210
+ }
174
211
  }
175
- lines.push('');
176
212
  }
177
- }
178
- catch { /* ignore */ }
179
- // 솔루션 통계 (1줄)
213
+ catch { /* ignore */ }
214
+ // [Priority 6] 솔루션 통계 (1줄, 저비용)
180
215
  try {
181
216
  const solCount = db.prepare('SELECT COUNT(*) as cnt FROM solutions WHERE project = ?').get(project)?.cnt || 0;
182
217
  if (solCount > 0) {
183
- lines.push(`Solutions: ${solCount} recorded (auto-injected on error)`);
184
- lines.push('');
218
+ const solLine = `\nSolutions: ${solCount} recorded (auto-injected on error)\n`;
219
+ if (tokenBudget > 10) {
220
+ lines.push(solLine);
221
+ tokenBudget -= estimateTokens(solLine);
222
+ }
185
223
  }
186
224
  }
187
225
  catch { /* solutions table may not exist */ }
package/dist/index.js CHANGED
@@ -278,6 +278,23 @@ function cosineSimilarity(a, b) {
278
278
  }
279
279
  return dot / (Math.sqrt(normA) * Math.sqrt(normB));
280
280
  }
281
+ // ===== 임베딩 저장 (재시도 포함) =====
282
+ async function storeEmbeddingWithRetry(db, entityType, entityId, text, retries = 2) {
283
+ for (let attempt = 0; attempt <= retries; attempt++) {
284
+ try {
285
+ const embedding = await generateEmbedding(text, 'passage');
286
+ if (embedding) {
287
+ const buffer = Buffer.from(new Float32Array(embedding).buffer);
288
+ db.prepare('INSERT OR REPLACE INTO embeddings_v4 (entity_type, entity_id, embedding) VALUES (?, ?, ?)')
289
+ .run(entityType, entityId, buffer);
290
+ return;
291
+ }
292
+ }
293
+ catch { /* retry */ }
294
+ if (attempt < retries)
295
+ await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));
296
+ }
297
+ }
281
298
  // ===== 유틸리티 함수 =====
282
299
  async function fileExists(filePath) {
283
300
  try {
@@ -892,9 +909,9 @@ async function handleTool(name, args) {
892
909
  const days = args.days || 7;
893
910
  const sessions = db.prepare(`
894
911
  SELECT * FROM sessions
895
- WHERE project = ? AND timestamp > datetime('now', '-${days} days')
912
+ WHERE project = ? AND timestamp > datetime('now', '-' || ? || ' days')
896
913
  ORDER BY timestamp DESC LIMIT ?
897
- `).all(project, limit);
914
+ `).all(project, days, limit);
898
915
  return {
899
916
  content: [{
900
917
  type: 'text',
@@ -928,10 +945,26 @@ async function handleTool(name, args) {
928
945
  // 모든 세션 가져와서 유사도 계산
929
946
  const allSessions = db.prepare(project
930
947
  ? 'SELECT * FROM sessions WHERE project = ? ORDER BY timestamp DESC LIMIT 100'
931
- : 'SELECT * FROM sessions ORDER BY timestamp DESC LIMIT 100').all(project ? [project] : []);
948
+ : 'SELECT * FROM sessions ORDER BY timestamp DESC LIMIT 100').all(...(project ? [project] : []));
949
+ // 사전 캐시된 임베딩 활용 (없으면 on-the-fly 생성 후 캐시)
932
950
  const scored = await Promise.all(allSessions.map(async (s) => {
933
- const text = `${s.last_work} ${s.current_status || ''}`;
934
- const emb = await generateEmbedding(text, 'passage');
951
+ const sessionId = s.id;
952
+ // 캐시된 임베딩 확인
953
+ const cached = db.prepare('SELECT embedding FROM embeddings_v4 WHERE entity_type = ? AND entity_id = ?').get('session', sessionId);
954
+ let emb = null;
955
+ if (cached?.embedding) {
956
+ emb = Array.from(new Float32Array(cached.embedding.buffer, cached.embedding.byteOffset, cached.embedding.byteLength / 4));
957
+ }
958
+ else {
959
+ const text = `${s.last_work} ${s.current_status || ''}`;
960
+ emb = await generateEmbedding(text, 'passage');
961
+ // 생성 성공 시 캐시 저장
962
+ if (emb) {
963
+ const buffer = Buffer.from(new Float32Array(emb).buffer);
964
+ db.prepare('INSERT OR REPLACE INTO embeddings_v4 (entity_type, entity_id, embedding) VALUES (?, ?, ?)')
965
+ .run('session', sessionId, buffer);
966
+ }
967
+ }
935
968
  const similarity = emb ? cosineSimilarity(queryEmbedding, emb) : 0;
936
969
  return { ...s, similarity };
937
970
  }));
@@ -1180,13 +1213,8 @@ async function handleTool(name, args) {
1180
1213
  INSERT INTO solutions (project, error_signature, error_message, solution, related_files, keywords)
1181
1214
  VALUES (?, ?, ?, ?, ?, ?)
1182
1215
  `).run(project || null, errorSignature, errorMessage || null, solution, relatedFiles ? JSON.stringify(relatedFiles) : null, keywords);
1183
- // 임베딩 저장 (시맨틱 검색용) - embeddings_v4 사용
1184
- generateEmbedding(`${errorSignature} ${errorMessage || ''} ${solution}`, 'passage').then(embedding => {
1185
- if (embedding) {
1186
- const buffer = Buffer.from(new Float32Array(embedding).buffer);
1187
- db.prepare('INSERT OR REPLACE INTO embeddings_v4 (entity_type, entity_id, embedding) VALUES (?, ?, ?)').run('solution', result.lastInsertRowid, buffer);
1188
- }
1189
- }).catch(() => { });
1216
+ // 임베딩 저장 (시맨틱 검색용, 재시도 포함)
1217
+ storeEmbeddingWithRetry(db, 'solution', result.lastInsertRowid, `${errorSignature} ${errorMessage || ''} ${solution}`);
1190
1218
  return {
1191
1219
  content: [{
1192
1220
  type: 'text',
@@ -1408,13 +1436,8 @@ async function handleTool(name, args) {
1408
1436
  VALUES (?, ?, ?, ?, ?, ?)
1409
1437
  `).run(content, memoryType, tags ? JSON.stringify(tags) : null, project || null, importance, null);
1410
1438
  const memoryId = result.lastInsertRowid;
1411
- // 임베딩 생성 (비동기)
1412
- generateEmbedding(content, 'passage').then(embedding => {
1413
- if (embedding) {
1414
- const buffer = Buffer.from(new Float32Array(embedding).buffer);
1415
- db.prepare('INSERT OR REPLACE INTO embeddings_v4 (entity_type, entity_id, embedding) VALUES (?, ?, ?)').run('memory', memoryId, buffer);
1416
- }
1417
- }).catch(() => { });
1439
+ // 임베딩 생성 (재시도 포함)
1440
+ storeEmbeddingWithRetry(db, 'memory', memoryId, content);
1418
1441
  // 관계 자동 생성
1419
1442
  if (relatedTo) {
1420
1443
  db.prepare(`
@@ -1645,10 +1668,10 @@ async function handleTool(name, args) {
1645
1668
  const project = args.project;
1646
1669
  const totalMemories = db.prepare(project
1647
1670
  ? 'SELECT COUNT(*) as count FROM memories WHERE project = ?'
1648
- : 'SELECT COUNT(*) as count FROM memories').get(project ? [project] : []).count;
1671
+ : 'SELECT COUNT(*) as count FROM memories').get(...(project ? [project] : [])).count;
1649
1672
  const byType = db.prepare(project
1650
1673
  ? 'SELECT memory_type as type, COUNT(*) as count FROM memories WHERE project = ? GROUP BY memory_type'
1651
- : 'SELECT memory_type as type, COUNT(*) as count FROM memories GROUP BY memory_type').all(project ? [project] : []);
1674
+ : 'SELECT memory_type as type, COUNT(*) as count FROM memories GROUP BY memory_type').all(...(project ? [project] : []));
1652
1675
  const byProject = db.prepare(`
1653
1676
  SELECT COALESCE(project, 'global') as project, COUNT(*) as count
1654
1677
  FROM memories GROUP BY project ORDER BY count DESC LIMIT 10
@@ -1663,13 +1686,13 @@ async function handleTool(name, args) {
1663
1686
  FROM memories
1664
1687
  ${project ? 'WHERE project = ?' : ''}
1665
1688
  ORDER BY created_at DESC LIMIT 5
1666
- `).all(project ? [project] : []);
1689
+ `).all(...(project ? [project] : []));
1667
1690
  const topAccessedMemories = db.prepare(`
1668
1691
  SELECT id, memory_type, content, access_count
1669
1692
  FROM memories
1670
1693
  ${project ? 'WHERE project = ?' : ''}
1671
1694
  ORDER BY access_count DESC LIMIT 5
1672
- `).all(project ? [project] : []);
1695
+ `).all(...(project ? [project] : []));
1673
1696
  return {
1674
1697
  content: [{
1675
1698
  type: 'text',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-session-continuity-mcp",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "description": "Session Continuity for Claude Code - Never re-explain your project again",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",