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.
- package/dist/hooks/session-end.js +105 -25
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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.
|
|
236
|
+
if (!line.includes('git commit'))
|
|
230
237
|
continue;
|
|
231
238
|
try {
|
|
232
|
-
const
|
|
233
|
-
//
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
//
|
|
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);
|