docuking-mcp 1.8.0 → 1.9.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/index.js +201 -43
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -508,7 +508,26 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
508
508
|
},
|
|
509
509
|
{
|
|
510
510
|
name: 'docuking_todo',
|
|
511
|
-
description:
|
|
511
|
+
description: `킹투두(King Todo) - 프로젝트 공식 할일을 z_DocuKing/z_King_Todo/z_King_Todo.md에 관리합니다.
|
|
512
|
+
|
|
513
|
+
**AI 내장 TodoWrite와 다름!** 킹투두는 웹에 동기화되고 팀과 공유됩니다.
|
|
514
|
+
|
|
515
|
+
**작성 형식:**
|
|
516
|
+
\`\`\`
|
|
517
|
+
1. ✅ **[태그] 키워드** 12.30/12.30
|
|
518
|
+
설명 (7칸 들여쓰기)
|
|
519
|
+
|
|
520
|
+
2. ⚙️ **[태그] 키워드** 12.30
|
|
521
|
+
설명
|
|
522
|
+
\`\`\`
|
|
523
|
+
|
|
524
|
+
**형식 규칙:**
|
|
525
|
+
- ✅ 완료 / ⚙️ 진행중
|
|
526
|
+
- **[태그] 키워드** 볼드 처리
|
|
527
|
+
- 날짜 1개 = 등록일(진행중), 2개 = 등록일/완료일(완료)
|
|
528
|
+
- 설명은 7칸 들여쓰기 ([ 기호 아래 정렬)
|
|
529
|
+
|
|
530
|
+
**사용자에게 "킹투두에 등록했습니다" 형식으로 보고하세요.**`,
|
|
512
531
|
inputSchema: {
|
|
513
532
|
type: 'object',
|
|
514
533
|
properties: {
|
|
@@ -523,7 +542,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
523
542
|
},
|
|
524
543
|
todo: {
|
|
525
544
|
type: 'string',
|
|
526
|
-
description: '할일 내용 (add 시 필수)',
|
|
545
|
+
description: '할일 내용 (add 시 필수). 형식: "[태그] 키워드 - 설명" 예: "[보안] API 인증 강화 - JWT 토큰 검증"',
|
|
527
546
|
},
|
|
528
547
|
todoId: {
|
|
529
548
|
type: 'number',
|
|
@@ -1318,11 +1337,13 @@ docuking_init을 먼저 실행하세요.`,
|
|
|
1318
1337
|
console.error('[DocuKing] Sync 시작 알림 실패:', e.message);
|
|
1319
1338
|
}
|
|
1320
1339
|
|
|
1321
|
-
// 서버에서 파일 해시 조회 (
|
|
1322
|
-
let
|
|
1340
|
+
// 서버에서 파일 해시 조회 (Git 스타일 동기화용)
|
|
1341
|
+
let serverPathToHash = {};
|
|
1342
|
+
let serverHashToPath = {};
|
|
1343
|
+
let serverAllPaths = [];
|
|
1323
1344
|
try {
|
|
1324
1345
|
const hashResponse = await fetch(
|
|
1325
|
-
`${API_ENDPOINT}/files/hashes?projectId=${projectId}`,
|
|
1346
|
+
`${API_ENDPOINT}/files/hashes-for-sync?projectId=${projectId}`,
|
|
1326
1347
|
{
|
|
1327
1348
|
headers: {
|
|
1328
1349
|
'Authorization': `Bearer ${apiKey}`,
|
|
@@ -1331,16 +1352,23 @@ docuking_init을 먼저 실행하세요.`,
|
|
|
1331
1352
|
);
|
|
1332
1353
|
if (hashResponse.ok) {
|
|
1333
1354
|
const hashData = await hashResponse.json();
|
|
1334
|
-
|
|
1355
|
+
serverPathToHash = hashData.pathToHash || {};
|
|
1356
|
+
serverHashToPath = hashData.hashToPath || {};
|
|
1357
|
+
serverAllPaths = hashData.allPaths || [];
|
|
1335
1358
|
}
|
|
1336
1359
|
} catch (e) {
|
|
1337
1360
|
// 해시 조회 실패는 무시 (처음 Push하는 경우 등)
|
|
1338
1361
|
console.error('[DocuKing] 파일 해시 조회 실패:', e.message);
|
|
1339
1362
|
}
|
|
1340
1363
|
|
|
1364
|
+
// 처리된 로컬 파일 경로 (서버 삭제용)
|
|
1365
|
+
const processedLocalPaths = new Set();
|
|
1366
|
+
let moved = 0;
|
|
1367
|
+
|
|
1341
1368
|
for (const file of filesToPush) {
|
|
1342
1369
|
current++;
|
|
1343
1370
|
const progress = `${current}/${total}`;
|
|
1371
|
+
processedLocalPaths.add(file.serverPath);
|
|
1344
1372
|
|
|
1345
1373
|
try {
|
|
1346
1374
|
// 파일 해시 계산 (변경 감지)
|
|
@@ -1360,15 +1388,50 @@ docuking_init을 먼저 실행하세요.`,
|
|
|
1360
1388
|
fileHash = crypto.createHash('sha256').update(content, 'utf-8').digest('hex');
|
|
1361
1389
|
}
|
|
1362
1390
|
|
|
1363
|
-
//
|
|
1364
|
-
|
|
1365
|
-
|
|
1391
|
+
// Git 스타일 동기화:
|
|
1392
|
+
// 1. 같은 경로 + 같은 해시 → 스킵 (변경 없음)
|
|
1393
|
+
if (serverPathToHash[file.serverPath] === fileHash) {
|
|
1394
|
+
const resultText = `${progress} ⊘ ${file.serverPath} (변경 없음)`;
|
|
1366
1395
|
results.push(resultText);
|
|
1367
1396
|
console.log(resultText);
|
|
1368
1397
|
skipped++;
|
|
1369
1398
|
continue;
|
|
1370
1399
|
}
|
|
1371
1400
|
|
|
1401
|
+
// 2. 같은 해시가 다른 경로에 있음 → Move (경로 변경)
|
|
1402
|
+
const existingPath = serverHashToPath[fileHash];
|
|
1403
|
+
if (existingPath && existingPath !== file.serverPath) {
|
|
1404
|
+
try {
|
|
1405
|
+
const moveResponse = await fetch(`${API_ENDPOINT}/files/move`, {
|
|
1406
|
+
method: 'POST',
|
|
1407
|
+
headers: {
|
|
1408
|
+
'Content-Type': 'application/json',
|
|
1409
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
1410
|
+
},
|
|
1411
|
+
body: JSON.stringify({
|
|
1412
|
+
projectId,
|
|
1413
|
+
oldPath: existingPath,
|
|
1414
|
+
newPath: file.serverPath,
|
|
1415
|
+
}),
|
|
1416
|
+
});
|
|
1417
|
+
|
|
1418
|
+
if (moveResponse.ok) {
|
|
1419
|
+
const resultText = `${progress} ↷ ${existingPath} → ${file.serverPath}`;
|
|
1420
|
+
results.push(resultText);
|
|
1421
|
+
console.log(resultText);
|
|
1422
|
+
moved++;
|
|
1423
|
+
// 이동된 파일의 해시 맵 업데이트
|
|
1424
|
+
delete serverHashToPath[fileHash];
|
|
1425
|
+
serverHashToPath[fileHash] = file.serverPath;
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1428
|
+
} catch (e) {
|
|
1429
|
+
console.error(`[DocuKing] Move 실패: ${e.message}, Upload로 대체`);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
// 3. 새 파일 또는 내용 변경 → Upload
|
|
1434
|
+
|
|
1372
1435
|
// 재시도 로직 (최대 3회)
|
|
1373
1436
|
let lastError = null;
|
|
1374
1437
|
let success = false;
|
|
@@ -1467,6 +1530,41 @@ docuking_init을 먼저 실행하세요.`,
|
|
|
1467
1530
|
}
|
|
1468
1531
|
}
|
|
1469
1532
|
|
|
1533
|
+
// 4. 서버에만 있고 로컬에 없는 파일 삭제 (Git rm과 동일)
|
|
1534
|
+
let deleted = 0;
|
|
1535
|
+
const deletedFiles = [];
|
|
1536
|
+
if (serverAllPaths.length > 0 && !isCoworker) {
|
|
1537
|
+
// 코워커가 아닌 경우에만 삭제 수행 (오너 전용)
|
|
1538
|
+
const pathsToDelete = serverAllPaths.filter(p => !processedLocalPaths.has(p));
|
|
1539
|
+
|
|
1540
|
+
if (pathsToDelete.length > 0) {
|
|
1541
|
+
console.log(`[DocuKing] 서버에만 있는 파일 ${pathsToDelete.length}개 삭제 중...`);
|
|
1542
|
+
|
|
1543
|
+
try {
|
|
1544
|
+
const deleteResponse = await fetch(`${API_ENDPOINT}/files/delete-batch`, {
|
|
1545
|
+
method: 'POST',
|
|
1546
|
+
headers: {
|
|
1547
|
+
'Content-Type': 'application/json',
|
|
1548
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
1549
|
+
},
|
|
1550
|
+
body: JSON.stringify({
|
|
1551
|
+
projectId,
|
|
1552
|
+
paths: pathsToDelete,
|
|
1553
|
+
}),
|
|
1554
|
+
});
|
|
1555
|
+
|
|
1556
|
+
if (deleteResponse.ok) {
|
|
1557
|
+
const deleteResult = await deleteResponse.json();
|
|
1558
|
+
deleted = deleteResult.deleted || 0;
|
|
1559
|
+
deletedFiles.push(...pathsToDelete.slice(0, deleted));
|
|
1560
|
+
console.log(`[DocuKing] ${deleted}개 파일 삭제 완료`);
|
|
1561
|
+
}
|
|
1562
|
+
} catch (e) {
|
|
1563
|
+
console.error('[DocuKing] 파일 삭제 실패:', e.message);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1470
1568
|
// Sync 완료 알림
|
|
1471
1569
|
try {
|
|
1472
1570
|
await fetch(`${API_ENDPOINT}/projects/${projectId}/sync/complete`, {
|
|
@@ -1484,9 +1582,10 @@ docuking_init을 먼저 실행하세요.`,
|
|
|
1484
1582
|
const failCount = results.filter(r => r.includes('✗')).length;
|
|
1485
1583
|
const skippedCount = skipped; // 이미 계산된 스킵 개수 사용
|
|
1486
1584
|
const excludedCount = excludedFiles.length;
|
|
1585
|
+
const movedCount = moved;
|
|
1487
1586
|
|
|
1488
1587
|
// 요약 정보
|
|
1489
|
-
let summary = `\n📦 커밋 메시지: "${message}"\n\n📊 처리 결과:\n - 총 파일: ${total}개\n - 업로드: ${successCount}개\n - 스킵 (변경 없음): ${skippedCount}개\n - 실패: ${failCount}개`;
|
|
1588
|
+
let summary = `\n📦 커밋 메시지: "${message}"\n\n📊 처리 결과:\n - 총 파일: ${total}개\n - 업로드: ${successCount}개\n - 이동: ${movedCount}개\n - 삭제: ${deleted}개\n - 스킵 (변경 없음): ${skippedCount}개\n - 실패: ${failCount}개`;
|
|
1490
1589
|
if (excludedCount > 0) {
|
|
1491
1590
|
summary += `\n - 제외 (압축/설치파일): ${excludedCount}개`;
|
|
1492
1591
|
}
|
|
@@ -1500,6 +1599,20 @@ docuking_init을 먼저 실행하세요.`,
|
|
|
1500
1599
|
resultText += `\n\n📤 업로드된 파일 (${successCount}개):\n${uploadedFiles.map(r => ` ${r.replace(/^\d+\/\d+ /, '')}`).join('\n')}`;
|
|
1501
1600
|
}
|
|
1502
1601
|
|
|
1602
|
+
// 이동된 파일이 있으면 표시 (Git 스타일)
|
|
1603
|
+
if (movedCount > 0) {
|
|
1604
|
+
const movedFiles = results.filter(r => r.includes('↷'));
|
|
1605
|
+
resultText += `\n\n↷ 이동된 파일 (${movedCount}개):\n${movedFiles.map(r => ` ${r.replace(/^\d+\/\d+ /, '')}`).join('\n')}`;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
// 삭제된 파일이 있으면 표시 (Git 스타일)
|
|
1609
|
+
if (deleted > 0) {
|
|
1610
|
+
resultText += `\n\n🗑️ 삭제된 파일 (${deleted}개, 로컬에 없음):\n${deletedFiles.slice(0, 20).map(f => ` ✗ ${f}`).join('\n')}`;
|
|
1611
|
+
if (deleted > 20) {
|
|
1612
|
+
resultText += `\n ... 외 ${deleted - 20}개`;
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1503
1616
|
// 스킵된 파일이 있으면 표시
|
|
1504
1617
|
if (skippedCount > 0) {
|
|
1505
1618
|
const skippedFiles = results.filter(r => r.includes('⊘'));
|
|
@@ -1829,10 +1942,15 @@ function getFileType(fileName) {
|
|
|
1829
1942
|
}
|
|
1830
1943
|
|
|
1831
1944
|
// 유틸: DocuKing 폴더 찾기 (docuking 포함, 대소문자 무관)
|
|
1945
|
+
// .docuking (설정 폴더)는 제외
|
|
1832
1946
|
function findDocuKingFolder(projectPath) {
|
|
1833
1947
|
try {
|
|
1834
1948
|
const entries = fs.readdirSync(projectPath, { withFileTypes: true });
|
|
1835
1949
|
for (const entry of entries) {
|
|
1950
|
+
// .docuking은 설정 폴더이므로 제외 (숨김 폴더)
|
|
1951
|
+
if (entry.name.startsWith('.')) {
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1836
1954
|
if (entry.isDirectory() && entry.name.toLowerCase().includes('docuking')) {
|
|
1837
1955
|
return entry.name;
|
|
1838
1956
|
}
|
|
@@ -2122,13 +2240,13 @@ function generatePlanId() {
|
|
|
2122
2240
|
return id;
|
|
2123
2241
|
}
|
|
2124
2242
|
|
|
2125
|
-
// docuking_todo 구현 -
|
|
2243
|
+
// docuking_todo 구현 - 킹투두 (단일 파일 누적)
|
|
2126
2244
|
async function handleTodo(args) {
|
|
2127
2245
|
const { localPath, action, todo, todoId } = args;
|
|
2128
2246
|
|
|
2129
|
-
//
|
|
2130
|
-
const todoBasePath = path.join(localPath, 'z_DocuKing', '
|
|
2131
|
-
const todoFilePath = path.join(todoBasePath, '
|
|
2247
|
+
// z_King_Todo 폴더 경로
|
|
2248
|
+
const todoBasePath = path.join(localPath, 'z_DocuKing', 'z_King_Todo');
|
|
2249
|
+
const todoFilePath = path.join(todoBasePath, 'z_King_Todo.md');
|
|
2132
2250
|
|
|
2133
2251
|
// 폴더 생성
|
|
2134
2252
|
if (!fs.existsSync(todoBasePath)) {
|
|
@@ -2139,8 +2257,7 @@ async function handleTodo(args) {
|
|
|
2139
2257
|
if (!fs.existsSync(todoFilePath)) {
|
|
2140
2258
|
const header = `# TODO 목록
|
|
2141
2259
|
|
|
2142
|
-
>
|
|
2143
|
-
> - [ ] 미완료 / - [x] 완료 (완료 시 날짜 추가)
|
|
2260
|
+
> 날짜 1개 = 등록일 (진행중) / 날짜 2개 = 등록일/완료일 (완료)
|
|
2144
2261
|
|
|
2145
2262
|
---
|
|
2146
2263
|
|
|
@@ -2161,8 +2278,9 @@ async function handleTodo(args) {
|
|
|
2161
2278
|
}
|
|
2162
2279
|
|
|
2163
2280
|
const now = new Date();
|
|
2164
|
-
const
|
|
2165
|
-
const
|
|
2281
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
2282
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
2283
|
+
const dateStr = `${month}.${day}`;
|
|
2166
2284
|
|
|
2167
2285
|
if (action === 'add') {
|
|
2168
2286
|
if (!todo) {
|
|
@@ -2172,7 +2290,39 @@ async function handleTodo(args) {
|
|
|
2172
2290
|
}
|
|
2173
2291
|
|
|
2174
2292
|
const newId = maxId + 1;
|
|
2175
|
-
|
|
2293
|
+
|
|
2294
|
+
// todo에서 태그와 키워드, 설명 분리
|
|
2295
|
+
// 형식: "[태그] 키워드 - 설명" 또는 "[태그] 키워드"
|
|
2296
|
+
let tag = '';
|
|
2297
|
+
let keyword = '';
|
|
2298
|
+
let description = '';
|
|
2299
|
+
|
|
2300
|
+
const tagMatch = todo.match(/^\[([^\]]+)\]\s*/);
|
|
2301
|
+
if (tagMatch) {
|
|
2302
|
+
tag = tagMatch[1];
|
|
2303
|
+
const rest = todo.slice(tagMatch[0].length);
|
|
2304
|
+
const descSplit = rest.split(/\s*[-–]\s*/);
|
|
2305
|
+
keyword = descSplit[0].trim();
|
|
2306
|
+
description = descSplit.slice(1).join(' - ').trim();
|
|
2307
|
+
} else {
|
|
2308
|
+
// 태그 없이 입력된 경우
|
|
2309
|
+
const descSplit = todo.split(/\s*[-–]\s*/);
|
|
2310
|
+
keyword = descSplit[0].trim();
|
|
2311
|
+
description = descSplit.slice(1).join(' - ').trim();
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
// 킹투두 형식으로 작성
|
|
2315
|
+
let newTodo;
|
|
2316
|
+
if (tag) {
|
|
2317
|
+
newTodo = `${newId}. ⚙️ **[${tag}] ${keyword}** ${dateStr}\n`;
|
|
2318
|
+
} else {
|
|
2319
|
+
newTodo = `${newId}. ⚙️ **${keyword}** ${dateStr}\n`;
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
if (description) {
|
|
2323
|
+
newTodo += ` ${description}\n`;
|
|
2324
|
+
}
|
|
2325
|
+
newTodo += '\n';
|
|
2176
2326
|
|
|
2177
2327
|
// 파일 끝에 추가
|
|
2178
2328
|
fs.appendFileSync(todoFilePath, newTodo, 'utf-8');
|
|
@@ -2180,10 +2330,10 @@ async function handleTodo(args) {
|
|
|
2180
2330
|
return {
|
|
2181
2331
|
content: [{
|
|
2182
2332
|
type: 'text',
|
|
2183
|
-
text: `✓
|
|
2333
|
+
text: `✓ 킹투두에 등록했습니다!
|
|
2184
2334
|
|
|
2185
|
-
📝 #${newId}: ${
|
|
2186
|
-
📅 등록: ${dateStr}
|
|
2335
|
+
📝 #${newId}: ${tag ? `[${tag}] ` : ''}${keyword}${description ? ` - ${description}` : ''}
|
|
2336
|
+
📅 등록: ${dateStr}
|
|
2187
2337
|
|
|
2188
2338
|
💡 완료 시: docuking_todo(action: "done", todoId: ${newId})`,
|
|
2189
2339
|
}],
|
|
@@ -2197,73 +2347,81 @@ async function handleTodo(args) {
|
|
|
2197
2347
|
};
|
|
2198
2348
|
}
|
|
2199
2349
|
|
|
2200
|
-
// 해당 번호의 TODO 찾아서
|
|
2201
|
-
const todoLinePattern = new RegExp(`^(${todoId}\\.
|
|
2350
|
+
// 해당 번호의 TODO 찾아서 ⚙️ -> ✅ 변경 및 날짜 추가
|
|
2351
|
+
const todoLinePattern = new RegExp(`^(${todoId}\\. )⚙️( \\*\\*.*\\*\\* )(\\d+\\.\\d+)(.*)$`, 'm');
|
|
2202
2352
|
const matched = content.match(todoLinePattern);
|
|
2203
2353
|
|
|
2204
2354
|
if (!matched) {
|
|
2355
|
+
// 이미 완료된 항목인지 확인
|
|
2356
|
+
const completedPattern = new RegExp(`^${todoId}\\. ✅`, 'm');
|
|
2357
|
+
if (completedPattern.test(content)) {
|
|
2358
|
+
return {
|
|
2359
|
+
content: [{ type: 'text', text: `킹투두 #${todoId}는 이미 완료 상태입니다.` }],
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
2205
2362
|
return {
|
|
2206
|
-
content: [{ type: 'text', text: `오류:
|
|
2363
|
+
content: [{ type: 'text', text: `오류: 킹투두 #${todoId}를 찾을 수 없습니다.` }],
|
|
2207
2364
|
};
|
|
2208
2365
|
}
|
|
2209
2366
|
|
|
2210
|
-
//
|
|
2367
|
+
// ⚙️ -> ✅ 변경 + 완료 날짜 추가 (등록일/완료일)
|
|
2211
2368
|
const updatedContent = content.replace(
|
|
2212
2369
|
todoLinePattern,
|
|
2213
|
-
`$
|
|
2370
|
+
`$1✅$2$3/${dateStr}$4`
|
|
2214
2371
|
);
|
|
2215
2372
|
|
|
2216
2373
|
fs.writeFileSync(todoFilePath, updatedContent, 'utf-8');
|
|
2217
2374
|
|
|
2218
2375
|
// 완료된 TODO 내용 추출
|
|
2219
|
-
const
|
|
2376
|
+
const keywordMatch = matched[2].match(/\*\*(.+)\*\*/);
|
|
2377
|
+
const todoKeyword = keywordMatch ? keywordMatch[1] : '';
|
|
2220
2378
|
|
|
2221
2379
|
return {
|
|
2222
2380
|
content: [{
|
|
2223
2381
|
type: 'text',
|
|
2224
|
-
text: `✓
|
|
2382
|
+
text: `✓ 킹투두 #${todoId} 완료!
|
|
2225
2383
|
|
|
2226
|
-
✅ ${
|
|
2227
|
-
📅 완료: ${dateStr}
|
|
2384
|
+
✅ ${todoKeyword}
|
|
2385
|
+
📅 완료: ${dateStr}`,
|
|
2228
2386
|
}],
|
|
2229
2387
|
};
|
|
2230
2388
|
}
|
|
2231
2389
|
|
|
2232
2390
|
if (action === 'list') {
|
|
2233
|
-
// 미완료 TODO
|
|
2234
|
-
const pendingPattern = /^(\d+)\.
|
|
2391
|
+
// 미완료(⚙️) TODO 추출
|
|
2392
|
+
const pendingPattern = /^(\d+)\. ⚙️ \*\*(.+)\*\* (\d+\.\d+)/gm;
|
|
2235
2393
|
const pendingTodos = [];
|
|
2236
2394
|
let listMatch;
|
|
2237
2395
|
while ((listMatch = pendingPattern.exec(content)) !== null) {
|
|
2238
|
-
pendingTodos.push({ id: listMatch[1],
|
|
2396
|
+
pendingTodos.push({ id: listMatch[1], keyword: listMatch[2], date: listMatch[3] });
|
|
2239
2397
|
}
|
|
2240
2398
|
|
|
2241
|
-
// 완료된 TODO 수 세기
|
|
2242
|
-
const completedCount = (content.match(
|
|
2399
|
+
// 완료된(✅) TODO 수 세기
|
|
2400
|
+
const completedCount = (content.match(/^(\d+)\. ✅/gm) || []).length;
|
|
2243
2401
|
|
|
2244
2402
|
if (pendingTodos.length === 0) {
|
|
2245
2403
|
return {
|
|
2246
2404
|
content: [{
|
|
2247
2405
|
type: 'text',
|
|
2248
|
-
text: `📋
|
|
2406
|
+
text: `📋 킹투두 미결: 없음
|
|
2249
2407
|
|
|
2250
|
-
✅
|
|
2251
|
-
📁 전체 기록: z_DocuKing/
|
|
2408
|
+
✅ 완료: ${completedCount}개
|
|
2409
|
+
📁 전체 기록: z_DocuKing/z_King_Todo/z_King_Todo.md`,
|
|
2252
2410
|
}],
|
|
2253
2411
|
};
|
|
2254
2412
|
}
|
|
2255
2413
|
|
|
2256
|
-
const listText = pendingTodos.map(t => ` #${t.id}: ${t.
|
|
2414
|
+
const listText = pendingTodos.map(t => ` #${t.id}: ${t.keyword} (${t.date})`).join('\n');
|
|
2257
2415
|
|
|
2258
2416
|
return {
|
|
2259
2417
|
content: [{
|
|
2260
2418
|
type: 'text',
|
|
2261
|
-
text: `📋
|
|
2419
|
+
text: `📋 킹투두 미결: ${pendingTodos.length}개
|
|
2262
2420
|
|
|
2263
2421
|
${listText}
|
|
2264
2422
|
|
|
2265
|
-
✅
|
|
2266
|
-
📁 전체 기록: z_DocuKing/
|
|
2423
|
+
✅ 완료: ${completedCount}개
|
|
2424
|
+
📁 전체 기록: z_DocuKing/z_King_Todo/z_King_Todo.md`,
|
|
2267
2425
|
}],
|
|
2268
2426
|
};
|
|
2269
2427
|
}
|