claude-session-continuity-mcp 1.9.4 → 1.9.5
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/dist/hooks/session-end.js +86 -34
- package/package.json +1 -1
|
@@ -58,44 +58,90 @@ function detectProject(cwd) {
|
|
|
58
58
|
}
|
|
59
59
|
return path.basename(workspaceRoot);
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* 마크다운 문법 제거 — 순수 텍스트로 변환
|
|
63
|
+
*/
|
|
64
|
+
function stripMarkdown(text) {
|
|
65
|
+
return text
|
|
66
|
+
.replace(/\*\*(.+?)\*\*/g, '$1') // **bold** → bold
|
|
67
|
+
.replace(/\*(.+?)\*/g, '$1') // *italic* → italic
|
|
68
|
+
.replace(/`([^`]+)`/g, '$1') // `code` → code
|
|
69
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // [link](url) → link
|
|
70
|
+
.replace(/#{1,6}\s*/g, '') // ## heading → heading
|
|
71
|
+
.trim();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 텍스트 행이 "노이즈"인지 판별
|
|
75
|
+
*/
|
|
76
|
+
function isNoiseLine(line) {
|
|
77
|
+
const trimmed = line.trim();
|
|
78
|
+
if (trimmed.length < 15)
|
|
79
|
+
return true; // 너무 짧음
|
|
80
|
+
if (trimmed.startsWith('|'))
|
|
81
|
+
return true; // 마크다운 테이블 행
|
|
82
|
+
if (trimmed.startsWith('```'))
|
|
83
|
+
return true; // 코드 블록 경계
|
|
84
|
+
if (trimmed.startsWith('---'))
|
|
85
|
+
return true; // 구분선
|
|
86
|
+
if (/^[-*+]\s*$/.test(trimmed))
|
|
87
|
+
return true; // 빈 리스트
|
|
88
|
+
if (/^#+\s*$/.test(trimmed))
|
|
89
|
+
return true; // 빈 헤딩
|
|
90
|
+
if (/^\s*```/.test(trimmed))
|
|
91
|
+
return true; // 들여쓴 코드 블록
|
|
92
|
+
if (/^(Sources?|참고|Note|주의)[:\s]/i.test(trimmed))
|
|
93
|
+
return true; // 메타 텍스트
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
61
96
|
/**
|
|
62
97
|
* 단일 텍스트(last_assistant_message 등)에서 의미있는 요약 추출
|
|
98
|
+
*
|
|
99
|
+
* 우선순위:
|
|
100
|
+
* 1. 구조화 마커 <!--SESSION:{"done":"..."}-->
|
|
101
|
+
* 2. 완료 문장 패턴 (I've completed..., 구현 완료 등)
|
|
102
|
+
* 3. 첫 의미있는 단락 (테이블/코드블록/리스트 제외)
|
|
63
103
|
*/
|
|
64
104
|
function extractSummaryFromText(content) {
|
|
65
105
|
if (!content || content.length < 10)
|
|
66
106
|
return '';
|
|
67
|
-
//
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
107
|
+
// 전처리: 테이블 행, 코드블록 제거 → 순수 텍스트
|
|
108
|
+
const cleanedContent = content
|
|
109
|
+
.replace(/```[\s\S]*?```/g, '') // 코드블록 제거
|
|
110
|
+
.split('\n')
|
|
111
|
+
.filter(line => !line.trim().startsWith('|') && !line.trim().startsWith('---'))
|
|
112
|
+
.join('\n');
|
|
113
|
+
// 전략 1: 구조화 마커 (CLAUDE.md 규칙을 따르는 경우)
|
|
114
|
+
const markerMatch = content.match(/<!--SESSION:(.*?)-->/s);
|
|
115
|
+
if (markerMatch?.[1]) {
|
|
116
|
+
try {
|
|
117
|
+
const parsed = JSON.parse(markerMatch[1]);
|
|
118
|
+
if (parsed.done && parsed.done.length > 5)
|
|
119
|
+
return stripMarkdown(parsed.done).slice(0, 200);
|
|
78
120
|
}
|
|
121
|
+
catch { /* malformed JSON, fall through */ }
|
|
122
|
+
}
|
|
123
|
+
// 전략 2: 완료/성과 문장 추출 (정제된 텍스트에서)
|
|
124
|
+
const completionMatch = cleanedContent.match(/(?:I've |I have |Successfully |completed |finished |implemented |fixed |created |added |updated |refactored |deployed |배포 완료|구현 완료|작업 완료|수정 완료|테스트 통과|빌드 성공)([^.!?\n]{5,150}[.!?]?)/i);
|
|
125
|
+
if (completionMatch) {
|
|
126
|
+
const sentence = stripMarkdown(completionMatch[0]).trim();
|
|
127
|
+
if (sentence.length > 15)
|
|
128
|
+
return sentence.slice(0, 200);
|
|
79
129
|
}
|
|
80
|
-
// 전략
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
83
|
-
const cleaned =
|
|
84
|
-
|
|
85
|
-
.
|
|
86
|
-
.slice(0, 3);
|
|
87
|
-
if (cleaned.length > 0)
|
|
88
|
-
return cleaned.join('; ').slice(0, 200);
|
|
130
|
+
// 전략 3: ✅ 마커 뒤 텍스트 (정제된 텍스트에서)
|
|
131
|
+
const checkMatch = cleanedContent.match(/✅\s*(.+)/);
|
|
132
|
+
if (checkMatch?.[1]) {
|
|
133
|
+
const cleaned = stripMarkdown(checkMatch[1]).trim();
|
|
134
|
+
if (cleaned.length > 10)
|
|
135
|
+
return cleaned.slice(0, 200);
|
|
89
136
|
}
|
|
90
|
-
// 전략
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return trimmed;
|
|
137
|
+
// 전략 4: 첫 의미있는 단락 (노이즈 라인 건너뜀)
|
|
138
|
+
const lines = cleanedContent.split('\n');
|
|
139
|
+
for (const line of lines) {
|
|
140
|
+
if (isNoiseLine(line))
|
|
141
|
+
continue;
|
|
142
|
+
const cleaned = stripMarkdown(line).trim();
|
|
143
|
+
if (cleaned.length > 20)
|
|
144
|
+
return cleaned.slice(0, 200);
|
|
99
145
|
}
|
|
100
146
|
return '';
|
|
101
147
|
}
|
|
@@ -139,19 +185,25 @@ async function readRecentAssistantMessages(transcriptPath, maxMessages = 5) {
|
|
|
139
185
|
}
|
|
140
186
|
/**
|
|
141
187
|
* 다음 할 일 추출 (텍스트에서)
|
|
188
|
+
* 테이블 행, 코드블록 내부는 제외
|
|
142
189
|
*/
|
|
143
190
|
function extractNextTasks(content) {
|
|
144
191
|
const nextTasks = [];
|
|
192
|
+
// 코드블록과 테이블 행 제거
|
|
193
|
+
const cleaned = content
|
|
194
|
+
.replace(/```[\s\S]*?```/g, '') // 코드블록 제거
|
|
195
|
+
.split('\n')
|
|
196
|
+
.filter(line => !line.trim().startsWith('|')) // 테이블 행 제거
|
|
197
|
+
.join('\n');
|
|
145
198
|
const nextPatterns = [
|
|
146
|
-
/(?:next
|
|
147
|
-
/(?:should|need to|필요|권장|추천)[:\s]*([^.!?\n]+)/gi,
|
|
199
|
+
/(?:next steps?|todo|remaining|다음 (?:단계|작업|할 일)|남은 작업|해야 할)[:\s]*([^.!?\n]{10,})/gi,
|
|
148
200
|
];
|
|
149
201
|
for (const pattern of nextPatterns) {
|
|
150
202
|
let match;
|
|
151
|
-
while ((match = pattern.exec(
|
|
203
|
+
while ((match = pattern.exec(cleaned)) !== null) {
|
|
152
204
|
if (match[1]) {
|
|
153
|
-
const task = match[1].trim().slice(0, 100);
|
|
154
|
-
if (task.length >
|
|
205
|
+
const task = stripMarkdown(match[1]).trim().slice(0, 100);
|
|
206
|
+
if (task.length > 10)
|
|
155
207
|
nextTasks.push(task);
|
|
156
208
|
}
|
|
157
209
|
}
|