claude-session-continuity-mcp 1.11.0 → 1.12.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.
@@ -7,6 +7,58 @@
7
7
  import * as fs from 'fs';
8
8
  import * as path from 'path';
9
9
  import Database from 'better-sqlite3';
10
+ // ===== 에러 감지 → 솔루션 자동 주입 =====
11
+ const ERROR_PATTERNS = [
12
+ /(?:error|Error|ERROR)\s*[:\[]/,
13
+ /(?:FAILED|FAIL|failed)\s/,
14
+ /(?:Cannot find|Module not found|No such file)/,
15
+ /(?:Permission denied|EACCES|EPERM)/,
16
+ /(?:command not found|ENOENT)/,
17
+ /exit code [1-9]/,
18
+ /(?:TypeError|SyntaxError|ReferenceError|RangeError)\s*:/,
19
+ /(?:FATAL|panic|segfault)/i,
20
+ ];
21
+ function extractErrorSignature(output) {
22
+ const lines = output.split('\n');
23
+ for (const line of lines) {
24
+ const trimmed = line.trim();
25
+ if (trimmed.length < 10)
26
+ continue;
27
+ for (const pattern of ERROR_PATTERNS) {
28
+ if (pattern.test(trimmed)) {
29
+ // 에러 라인에서 시그니처 추출 (파일 경로/라인번호 제거, 핵심만)
30
+ return trimmed
31
+ .replace(/\s+at\s+.+$/, '') // stack trace 제거
32
+ .replace(/\(.*?\)/g, '') // 경로 괄호 제거
33
+ .slice(0, 80)
34
+ .trim();
35
+ }
36
+ }
37
+ }
38
+ return null;
39
+ }
40
+ function searchSolutions(db, project, errorSig) {
41
+ try {
42
+ // 에러 키워드 추출 (3글자 이상 단어)
43
+ const keywords = errorSig.split(/\s+/).filter(w => w.length > 3).slice(0, 3);
44
+ if (keywords.length === 0)
45
+ return [];
46
+ const likeConditions = keywords.map(() => 'error_signature LIKE ?').join(' OR ');
47
+ const likeParams = keywords.map(k => `%${k}%`);
48
+ return db.prepare(`
49
+ SELECT error_signature, solution, project, created_at
50
+ FROM solutions
51
+ WHERE (${likeConditions})
52
+ ORDER BY
53
+ CASE WHEN project = ? THEN 0 ELSE 1 END,
54
+ created_at DESC
55
+ LIMIT 2
56
+ `).all(...likeParams, project);
57
+ }
58
+ catch {
59
+ return [];
60
+ }
61
+ }
10
62
  function detectWorkspaceRoot(cwd) {
11
63
  let current = cwd;
12
64
  const root = path.parse(current).root;
@@ -107,10 +159,40 @@ async function main() {
107
159
  inputData += chunk;
108
160
  }
109
161
  const input = inputData ? JSON.parse(inputData) : {};
162
+ const toolName = input.tool_name;
163
+ if (!toolName) {
164
+ process.exit(0);
165
+ }
166
+ // Bash 에러 감지 → 솔루션 자동 주입
167
+ if (toolName === 'Bash' && input.tool_result) {
168
+ const errorSig = extractErrorSignature(input.tool_result);
169
+ if (errorSig) {
170
+ const cwd = input.cwd || process.cwd();
171
+ const project = detectProject(cwd);
172
+ const dbPath = getDbPath(cwd);
173
+ if (fs.existsSync(dbPath)) {
174
+ try {
175
+ const db = new Database(dbPath, { readonly: true });
176
+ const solutions = searchSolutions(db, project, errorSig);
177
+ db.close();
178
+ if (solutions.length > 0) {
179
+ const lines = ['## Past solutions for similar error\n'];
180
+ for (const s of solutions) {
181
+ const sol = s.solution.length > 100 ? s.solution.slice(0, 100) + '...' : s.solution;
182
+ const date = s.created_at?.slice(0, 10) || '';
183
+ lines.push(`- **${s.error_signature.slice(0, 60)}** → ${sol} (${s.project}, ${date})`);
184
+ }
185
+ console.log(`\n<past-solution>\n${lines.join('\n')}\n</past-solution>\n`);
186
+ }
187
+ }
188
+ catch { /* ignore */ }
189
+ }
190
+ }
191
+ process.exit(0);
192
+ }
110
193
  const TRACKED_TOOLS = ['Edit', 'Write', 'Read', 'Glob', 'Grep'];
111
194
  const IGNORED_PATTERNS = ['node_modules', '.git/', 'dist/', 'build/', '.next/', 'coverage/', '.DS_Store'];
112
- const toolName = input.tool_name;
113
- if (!toolName || !TRACKED_TOOLS.includes(toolName)) {
195
+ if (!TRACKED_TOOLS.includes(toolName)) {
114
196
  process.exit(0);
115
197
  }
116
198
  // Read/Glob/Grep에서도 파일 경로 추출
@@ -237,6 +237,7 @@ async function parseTranscriptSinglePass(transcriptPath) {
237
237
  decisions: [],
238
238
  userRequests: { firstRequest: '', allRequests: [] },
239
239
  recentAssistantMessages: [],
240
+ errorFixPairs: [],
240
241
  };
241
242
  if (!transcriptPath || !fs.existsSync(transcriptPath))
242
243
  return result;
@@ -354,7 +355,11 @@ async function parseTranscriptSinglePass(transcriptPath) {
354
355
  const errorStr = stripMarkdown(errorMatch[0]).slice(0, 80);
355
356
  const fixLine = recentEntries[j].text.split('\n').find(l => fixRe.test(l));
356
357
  const fixStr = fixLine ? stripMarkdown(fixLine).slice(0, 80) : 'resolved';
357
- pairSet.add(`${errorStr} → ${fixStr}`);
358
+ const pairKey = `${errorStr} → ${fixStr}`;
359
+ if (!pairSet.has(pairKey)) {
360
+ pairSet.add(pairKey);
361
+ result.errorFixPairs.push({ error: errorStr, fix: fixStr });
362
+ }
358
363
  break;
359
364
  }
360
365
  }
@@ -427,6 +432,7 @@ async function main() {
427
432
  commitMessages: [], errorsSolved: [], decisions: [],
428
433
  userRequests: { firstRequest: '', allRequests: [] },
429
434
  recentAssistantMessages: [],
435
+ errorFixPairs: [],
430
436
  };
431
437
  if (input.transcript_path) {
432
438
  transcript = await parseTranscriptSinglePass(input.transcript_path);
@@ -547,10 +553,25 @@ async function main() {
547
553
  INSERT OR REPLACE INTO active_context (project, current_state, recent_files, updated_at)
548
554
  VALUES (?, ?, ?, datetime('now'))
549
555
  `).run(project, lastWork, JSON.stringify(modifiedFiles.slice(0, 15)));
556
+ // 에러→솔루션 자동 기록 (solutions 테이블)
557
+ let solutionsRecorded = 0;
558
+ if (transcript.errorFixPairs.length > 0) {
559
+ try {
560
+ for (const pair of transcript.errorFixPairs) {
561
+ const existing = db.prepare('SELECT id FROM solutions WHERE project = ? AND error_signature = ? LIMIT 1').get(project, pair.error);
562
+ if (!existing) {
563
+ db.prepare('INSERT INTO solutions (project, error_signature, solution) VALUES (?, ?, ?)').run(project, pair.error, pair.fix);
564
+ solutionsRecorded++;
565
+ }
566
+ }
567
+ }
568
+ catch { /* solutions table may not exist */ }
569
+ }
550
570
  db.close();
551
571
  console.log(`[SessionEnd] Saved session for ${project}`);
552
572
  console.log(` Last work: ${lastWork.slice(0, 80)}`);
553
573
  console.log(` Commits: ${commitMessages.length}, Decisions: ${decisions.length}, Errors: ${errorsSolved.length}`);
574
+ console.log(` Solutions auto-recorded: ${solutionsRecorded}`);
554
575
  console.log(` Modified files: ${modifiedFiles.length}`);
555
576
  console.log(` Next tasks: ${nextTasks.length}`);
556
577
  process.exit(0);
@@ -176,6 +176,15 @@ function loadContext(dbPath, project) {
176
176
  }
177
177
  }
178
178
  catch { /* ignore */ }
179
+ // 솔루션 통계 (1줄)
180
+ try {
181
+ const solCount = db.prepare('SELECT COUNT(*) as cnt FROM solutions WHERE project = ?').get(project)?.cnt || 0;
182
+ if (solCount > 0) {
183
+ lines.push(`Solutions: ${solCount} recorded (auto-injected on error)`);
184
+ lines.push('');
185
+ }
186
+ }
187
+ catch { /* solutions table may not exist */ }
179
188
  db.close();
180
189
  lines.push('---');
181
190
  lines.push('_Auto-injected by session-continuity v2. Use `session_end` when done._');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-session-continuity-mcp",
3
- "version": "1.11.0",
3
+ "version": "1.12.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",