claude-session-continuity-mcp 1.10.0 → 1.10.1

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.
@@ -134,11 +134,13 @@ function extractSummaryFromText(content) {
134
134
  if (cleaned.length > 10)
135
135
  return cleaned.slice(0, 200);
136
136
  }
137
- // 전략 4: 첫 헤딩 제목 (## 제목) 짧아도 허용 (성과 요약인 경우 많음)
137
+ // 전략 4: 첫 헤딩 제목 — 단, 일반적인 섹션 헤딩은 제외
138
138
  const headingMatch = cleanedContent.match(/^#{1,3}\s+(.+)$/m);
139
139
  if (headingMatch?.[1]) {
140
140
  const title = stripMarkdown(headingMatch[1]).trim();
141
- if (title.length > 5)
141
+ // "결과 요약", "평가", "분석" 같은 일반 헤딩은 의미없는 요약이므로 건너뜀
142
+ const genericHeadings = /^(결과|요약|분석|평가|결론|테스트|현재|문제|핵심|다음|참고|MCP|Overview|Summary|Result|Analysis|Test)/i;
143
+ if (title.length > 5 && !genericHeadings.test(title))
142
144
  return title.slice(0, 200);
143
145
  }
144
146
  // 전략 5: 첫 의미있는 단락 (노이즈 라인 건너뜀)
@@ -222,30 +224,45 @@ async function extractCommitMessages(transcriptPath) {
222
224
  if (!transcriptPath || !fs.existsSync(transcriptPath))
223
225
  return [];
224
226
  const commits = [];
227
+ // git commit -m "message" 또는 heredoc 패턴 (JSON 파싱 후 적용)
228
+ const commitPatterns = [
229
+ /git commit.*?-m\s*"\$\(cat <<'?EOF'?\n(.+?)(?:\n\n|\nCo-Authored|\nEOF)/s,
230
+ /git commit.*?-m\s*["']([^"'\n]{10,150})["']/,
231
+ ];
225
232
  try {
226
233
  const fileStream = fs.createReadStream(transcriptPath, { encoding: 'utf-8' });
227
234
  const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity });
228
235
  for await (const line of rl) {
229
- if (!line.trim())
236
+ if (!line.includes('git commit'))
230
237
  continue;
231
238
  try {
232
- const content = line;
233
- // git commit -m "message" 또는 heredoc 패턴
234
- const commitPatterns = [
235
- /git commit.*?-m\s*["']([^"']{10,150})["']/,
236
- /git commit.*?-m\s*"\$\(cat <<'?EOF'?\n([\s\S]{10,150}?)(?:\n\s*Co-Authored|\nEOF)/,
237
- ];
238
- for (const pattern of commitPatterns) {
239
- const match = content.match(pattern);
240
- if (match?.[1]) {
241
- const msg = match[1].trim().split('\n')[0]; // 첫 줄만
242
- if (msg.length > 10 && !msg.startsWith('Co-Authored')) {
243
- commits.push(msg.slice(0, 150));
239
+ const entry = JSON.parse(line);
240
+ // tool_use 블록에서 command 추출
241
+ const content = entry.message?.content;
242
+ if (!Array.isArray(content))
243
+ continue;
244
+ for (const block of content) {
245
+ if (block.type !== 'tool_use')
246
+ continue;
247
+ const cmd = block.input?.command;
248
+ if (!cmd || !cmd.includes('-m'))
249
+ continue;
250
+ // git commit이 명령어의 시작이거나 && 이후에 나와야 함
251
+ if (!/(?:^|&&\s*)git\s+commit/.test(cmd))
252
+ continue;
253
+ for (const pattern of commitPatterns) {
254
+ const match = cmd.match(pattern);
255
+ if (match?.[1]) {
256
+ const msg = match[1].trim().split('\n')[0]; // 첫 줄만
257
+ if (msg.length > 10 && !msg.startsWith('Co-Authored')) {
258
+ commits.push(msg.slice(0, 150));
259
+ }
260
+ break;
244
261
  }
245
262
  }
246
263
  }
247
264
  }
248
- catch { /* skip */ }
265
+ catch { /* skip malformed lines */ }
249
266
  }
250
267
  }
251
268
  catch { /* file read error */ }
@@ -328,6 +345,58 @@ async function extractDecisions(transcriptPath) {
328
345
  }
329
346
  return [...new Set(decisions)].slice(0, 3);
330
347
  }
348
+ /**
349
+ * transcript에서 첫 사용자 요청 추출 (세션의 핵심 목적)
350
+ */
351
+ async function extractUserRequest(transcriptPath) {
352
+ if (!transcriptPath || !fs.existsSync(transcriptPath))
353
+ return '';
354
+ try {
355
+ const fileStream = fs.createReadStream(transcriptPath, { encoding: 'utf-8' });
356
+ const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity });
357
+ for await (const line of rl) {
358
+ if (!line.trim())
359
+ continue;
360
+ try {
361
+ const entry = JSON.parse(line);
362
+ if (entry.type !== 'human' && entry.type !== 'user')
363
+ continue;
364
+ const content = entry.message?.content;
365
+ let text = '';
366
+ if (typeof content === 'string') {
367
+ text = content;
368
+ }
369
+ else if (Array.isArray(content)) {
370
+ text = content
371
+ .filter((b) => b.type === 'text')
372
+ .map((b) => b.text)
373
+ .join('\n');
374
+ }
375
+ // system-reminder 태그 제거
376
+ text = text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '').trim();
377
+ if (text.length < 5)
378
+ continue;
379
+ // 시스템/메타 메시지 스킵
380
+ if (text.startsWith('[Request interrupted'))
381
+ continue;
382
+ if (text.startsWith('This session is being continued'))
383
+ continue;
384
+ // "Implement the following plan:" → 실제 플랜 제목만 추출
385
+ const planMatch = text.match(/^Implement the following plan:\s*\n+#\s*(.+)/);
386
+ if (planMatch)
387
+ return planMatch[1].trim().slice(0, 200);
388
+ // 긴 텍스트는 첫 줄만 (플랜, 컨텍스트 덤프 등)
389
+ const firstLine = text.split('\n')[0].trim();
390
+ if (firstLine.length > 200)
391
+ return firstLine.slice(0, 200);
392
+ return stripMarkdown(firstLine).slice(0, 200);
393
+ }
394
+ catch { /* skip */ }
395
+ }
396
+ }
397
+ catch { /* file read error */ }
398
+ return '';
399
+ }
331
400
  /**
332
401
  * 최근 assistant 메시지에서 액션 동사 포함 라인 추출 (lastWork 폴백)
333
402
  */
@@ -381,20 +450,31 @@ async function main() {
381
450
  let errorsSolved = [];
382
451
  let decisions = [];
383
452
  // Phase 1: transcript_path에서 고품질 데이터 추출
453
+ let userRequest = '';
384
454
  if (input.transcript_path) {
385
- // git commit 메시지 추출 (가장 고품질 요약)
386
- commitMessages = await extractCommitMessages(input.transcript_path);
387
- // 에러→해결 쌍 추출
388
- errorsSolved = await extractErrorFixPairs(input.transcript_path);
389
- // 결정 사항 추출
390
- decisions = await extractDecisions(input.transcript_path);
455
+ [commitMessages, errorsSolved, decisions, userRequest] = await Promise.all([
456
+ extractCommitMessages(input.transcript_path),
457
+ extractErrorFixPairs(input.transcript_path),
458
+ extractDecisions(input.transcript_path),
459
+ extractUserRequest(input.transcript_path),
460
+ ]);
391
461
  }
392
462
  // Phase 2: lastWork 결정 (우선순위 폴백)
393
- // 2a: 커밋 메시지 기반 (가장 신뢰도 높음)
394
- if (commitMessages.length > 0) {
463
+ // 2a: 사용자 요청 + 커밋 메시지 조합 (가장 이상적)
464
+ if (userRequest && commitMessages.length > 0) {
465
+ lastWork = `${userRequest} → ${commitMessages.slice(0, 2).join('; ')}`;
466
+ if (lastWork.length > 250)
467
+ lastWork = lastWork.slice(0, 250);
468
+ }
469
+ // 2b: 커밋 메시지만 (사용자 요청 없을 때)
470
+ else if (commitMessages.length > 0) {
395
471
  lastWork = commitMessages.slice(0, 3).join('; ');
396
472
  }
397
- // 2b: last_assistant_message에서 추출
473
+ // 2c: 사용자 요청만 (커밋 없을 때)
474
+ else if (userRequest) {
475
+ lastWork = userRequest;
476
+ }
477
+ // 2d: last_assistant_message에서 추출
398
478
  if (!lastWork && input.last_assistant_message) {
399
479
  lastWork = extractSummaryFromText(input.last_assistant_message);
400
480
  nextTasks = extractNextTasks(input.last_assistant_message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-session-continuity-mcp",
3
- "version": "1.10.0",
3
+ "version": "1.10.1",
4
4
  "description": "Session Continuity for Claude Code - Never re-explain your project again",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",