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
|
-
|
|
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
|
-
|
|
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._');
|