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.
@@ -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
- // 전략 1: 또는 ## Summary 요약 섹션
68
- const summaryPatterns = [
69
- /✅\s*(.+?)(?:\n\n|\n(?=[#*-]))/s,
70
- /(?:##?\s*(?:Summary|변경\s*사항|작업\s*완료|결과))\s*\n([\s\S]+?)(?:\n\n|\n(?=##))/i,
71
- ];
72
- for (const pattern of summaryPatterns) {
73
- const match = content.match(pattern);
74
- if (match?.[1]) {
75
- const summary = match[1].replace(/\n/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 200);
76
- if (summary.length > 10)
77
- return summary;
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
- // 전략 2: 볼드 텍스트(**...**) 추출
81
- const boldMatches = content.match(/\*\*(.+?)\*\*/g);
82
- if (boldMatches && boldMatches.length > 0) {
83
- const cleaned = boldMatches
84
- .map(b => b.replace(/\*\*/g, ''))
85
- .filter(b => b.length > 5 && b.length < 100)
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
- // 전략 3: 첫 의미있는
91
- const firstLine = content.split('\n').find(line => {
92
- const trimmed = line.trim();
93
- return trimmed.length > 10 && !trimmed.startsWith('#') && !trimmed.startsWith('```');
94
- });
95
- if (firstLine) {
96
- const trimmed = firstLine.trim().slice(0, 200);
97
- if (trimmed.length > 10)
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|todo|remaining|다음|남은|해야|예정)[:\s]*([^.!?\n]+)/gi,
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(content)) !== null) {
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 > 5)
205
+ const task = stripMarkdown(match[1]).trim().slice(0, 100);
206
+ if (task.length > 10)
155
207
  nextTasks.push(task);
156
208
  }
157
209
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-session-continuity-mcp",
3
- "version": "1.9.4",
3
+ "version": "1.9.5",
4
4
  "description": "Session Continuity for Claude Code - Never re-explain your project again",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",