docuking-mcp 1.9.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.
Files changed (2) hide show
  1. package/index.js +107 -8
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1337,11 +1337,13 @@ docuking_init을 먼저 실행하세요.`,
1337
1337
  console.error('[DocuKing] Sync 시작 알림 실패:', e.message);
1338
1338
  }
1339
1339
 
1340
- // 서버에서 파일 해시 조회 (변경 감지용)
1341
- let serverFileHashes = {};
1340
+ // 서버에서 파일 해시 조회 (Git 스타일 동기화용)
1341
+ let serverPathToHash = {};
1342
+ let serverHashToPath = {};
1343
+ let serverAllPaths = [];
1342
1344
  try {
1343
1345
  const hashResponse = await fetch(
1344
- `${API_ENDPOINT}/files/hashes?projectId=${projectId}`,
1346
+ `${API_ENDPOINT}/files/hashes-for-sync?projectId=${projectId}`,
1345
1347
  {
1346
1348
  headers: {
1347
1349
  'Authorization': `Bearer ${apiKey}`,
@@ -1350,16 +1352,23 @@ docuking_init을 먼저 실행하세요.`,
1350
1352
  );
1351
1353
  if (hashResponse.ok) {
1352
1354
  const hashData = await hashResponse.json();
1353
- serverFileHashes = hashData.hashes || {};
1355
+ serverPathToHash = hashData.pathToHash || {};
1356
+ serverHashToPath = hashData.hashToPath || {};
1357
+ serverAllPaths = hashData.allPaths || [];
1354
1358
  }
1355
1359
  } catch (e) {
1356
1360
  // 해시 조회 실패는 무시 (처음 Push하는 경우 등)
1357
1361
  console.error('[DocuKing] 파일 해시 조회 실패:', e.message);
1358
1362
  }
1359
1363
 
1364
+ // 처리된 로컬 파일 경로 (서버 삭제용)
1365
+ const processedLocalPaths = new Set();
1366
+ let moved = 0;
1367
+
1360
1368
  for (const file of filesToPush) {
1361
1369
  current++;
1362
1370
  const progress = `${current}/${total}`;
1371
+ processedLocalPaths.add(file.serverPath);
1363
1372
 
1364
1373
  try {
1365
1374
  // 파일 해시 계산 (변경 감지)
@@ -1379,15 +1388,50 @@ docuking_init을 먼저 실행하세요.`,
1379
1388
  fileHash = crypto.createHash('sha256').update(content, 'utf-8').digest('hex');
1380
1389
  }
1381
1390
 
1382
- // 변경 감지: 서버에 같은 해시가 있으면 스킵
1383
- if (serverFileHashes[file.path] === fileHash) {
1384
- const resultText = `${progress} ⊘ ${file.path} (변경 없음)`;
1391
+ // Git 스타일 동기화:
1392
+ // 1. 같은 경로 + 같은 해시 → 스킵 (변경 없음)
1393
+ if (serverPathToHash[file.serverPath] === fileHash) {
1394
+ const resultText = `${progress} ⊘ ${file.serverPath} (변경 없음)`;
1385
1395
  results.push(resultText);
1386
1396
  console.log(resultText);
1387
1397
  skipped++;
1388
1398
  continue;
1389
1399
  }
1390
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
+
1391
1435
  // 재시도 로직 (최대 3회)
1392
1436
  let lastError = null;
1393
1437
  let success = false;
@@ -1486,6 +1530,41 @@ docuking_init을 먼저 실행하세요.`,
1486
1530
  }
1487
1531
  }
1488
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
+
1489
1568
  // Sync 완료 알림
1490
1569
  try {
1491
1570
  await fetch(`${API_ENDPOINT}/projects/${projectId}/sync/complete`, {
@@ -1503,9 +1582,10 @@ docuking_init을 먼저 실행하세요.`,
1503
1582
  const failCount = results.filter(r => r.includes('✗')).length;
1504
1583
  const skippedCount = skipped; // 이미 계산된 스킵 개수 사용
1505
1584
  const excludedCount = excludedFiles.length;
1585
+ const movedCount = moved;
1506
1586
 
1507
1587
  // 요약 정보
1508
- 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}개`;
1509
1589
  if (excludedCount > 0) {
1510
1590
  summary += `\n - 제외 (압축/설치파일): ${excludedCount}개`;
1511
1591
  }
@@ -1519,6 +1599,20 @@ docuking_init을 먼저 실행하세요.`,
1519
1599
  resultText += `\n\n📤 업로드된 파일 (${successCount}개):\n${uploadedFiles.map(r => ` ${r.replace(/^\d+\/\d+ /, '')}`).join('\n')}`;
1520
1600
  }
1521
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
+
1522
1616
  // 스킵된 파일이 있으면 표시
1523
1617
  if (skippedCount > 0) {
1524
1618
  const skippedFiles = results.filter(r => r.includes('⊘'));
@@ -1848,10 +1942,15 @@ function getFileType(fileName) {
1848
1942
  }
1849
1943
 
1850
1944
  // 유틸: DocuKing 폴더 찾기 (docuking 포함, 대소문자 무관)
1945
+ // .docuking (설정 폴더)는 제외
1851
1946
  function findDocuKingFolder(projectPath) {
1852
1947
  try {
1853
1948
  const entries = fs.readdirSync(projectPath, { withFileTypes: true });
1854
1949
  for (const entry of entries) {
1950
+ // .docuking은 설정 폴더이므로 제외 (숨김 폴더)
1951
+ if (entry.name.startsWith('.')) {
1952
+ continue;
1953
+ }
1855
1954
  if (entry.isDirectory() && entry.name.toLowerCase().includes('docuking')) {
1856
1955
  return entry.name;
1857
1956
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docuking-mcp",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "description": "DocuKing MCP Server - AI 시대의 문서 협업 플랫폼",
5
5
  "type": "module",
6
6
  "main": "index.js",