docuking-mcp 3.0.0 → 3.1.0

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/handlers/sync.js +89 -55
  2. package/package.json +1 -1
package/handlers/sync.js CHANGED
@@ -482,33 +482,13 @@ docuking_init을 먼저 실행하세요.`,
482
482
  }
483
483
 
484
484
  // ========================================
485
- // 로컬 파일 경로 목록 저장 (Pull 전에!)
486
- // soft-delete에서 "로컬에 없는 파일" 판단에 사용
487
- // Pull이 파일을 내려받아도 이 목록은 변하지 않음
485
+ // Pull 선행 제거 (2026-01-24)
488
486
  // ========================================
489
- const localPathsBeforePull = new Set(filesToPush.map(f => f.serverPath));
490
- console.error(`[DocuKing] Pull 로컬 파일 ${localPathsBeforePull.size}개 기록됨`);
491
-
492
- // ========================================
493
- // Pull 실행 (git pull && git push 패턴)
487
+ // 이전: Push 전에 Pull 실행 (git pull && git push 패턴)
488
+ // 문제: Pull 로컬 변경사항(삭제, 수정)을 덮어씀 → 좀비 파일 부활
489
+ // 해결: Push는 Push만. Pull은 사용자가 별도로 호출.
494
490
  // ========================================
495
- let pullResultText = '';
496
- try {
497
- const pullResult = await handlePullInternal({ localPath, filePath });
498
- pullResultText = pullResult.text;
499
- } catch (e) {
500
- return {
501
- content: [
502
- {
503
- type: 'text',
504
- text: `❌ Pull 실패로 Push를 중단합니다.
505
- 오류: ${e.message}
506
-
507
- 먼저 Pull 문제를 해결한 후 다시 시도하세요.`,
508
- },
509
- ],
510
- };
511
- }
491
+ console.error(`[DocuKing] Push 시작 (Pull 선행 없음 - 로컬 우선)`);
512
492
 
513
493
  if (filesToPush.length === 0 && emptyFolders.length === 0) {
514
494
  return {
@@ -803,23 +783,80 @@ docuking_init을 먼저 실행하세요.`,
803
783
  }
804
784
 
805
785
  // ========================================
806
- // 4. 자동 삭제 비활성화 (2026-01-17 버그 수정)
786
+ // 4. 인덱스 기반 삭제 전파 (2026-01-24 재구현)
807
787
  // ========================================
808
- // 이전 로직: "서버에 있고 로컬에 없으면 삭제"
809
- // 문제: 다른 프로젝트에서 Pull하면 서버 파일이 삭제됨
810
- // 해결: 자동 삭제 제거, docuking_delete로만 명시적 삭제
811
- //
812
- // 삭제는 명시적으로만:
813
- // - 사용자가 "이 파일 삭제해줘" 요청 시
814
- // - docuking_delete MCP 도구 사용
788
+ // 이전 문제: "서버에 있고 로컬에 없으면 삭제" → 다른 프로젝트에서 문제
789
+ // 해결: 인덱스 기반으로 판단
790
+ // - 인덱스에 있었는데 + 로컬에 없음 = 의도적 삭제 → 서버에서도 삭제
791
+ // - 인덱스에 없었고 + 로컬에 없음 = 처음부터 없던 파일 → 무시
815
792
  // ========================================
816
793
  let deleted = 0;
817
794
  const deletedFilePaths = [];
818
795
  let protectedFiles = [];
819
796
 
820
- // 자동 삭제 로직 비활성화 - 아래 코드는 주석 처리
821
- // "로컬에 없으면 삭제" 로직은 위험하므로 제거
822
- console.error(`[DocuKing] 자동 삭제 비활성화됨 (명시적 삭제만 허용: docuking_delete 사용)`);
797
+ // 인덱스에서 삭제된 파일 찾기 (인덱스에 있었는데 로컬에 없는 파일)
798
+ const locallyDeletedFiles = [];
799
+ for (const indexPath of Object.keys(localIndex.files)) {
800
+ // Push 대상 폴더 내의 파일인지 확인
801
+ let isInPushTarget = false;
802
+ let fullPath = '';
803
+
804
+ for (const target of pushTargetFolders) {
805
+ if (indexPath.startsWith(target.serverPrefix)) {
806
+ isInPushTarget = true;
807
+ const relativePath = indexPath.slice(target.serverPrefix.length + 1);
808
+ fullPath = path.join(target.localPath, relativePath);
809
+ break;
810
+ }
811
+ }
812
+
813
+ if (!isInPushTarget) continue;
814
+
815
+ // 로컬에 파일이 없으면 = 삭제된 파일
816
+ if (!fs.existsSync(fullPath)) {
817
+ locallyDeletedFiles.push(indexPath);
818
+ console.error(`[DocuKing] 삭제 감지: ${indexPath} (인덱스에 있었으나 로컬에 없음)`);
819
+ }
820
+ }
821
+
822
+ // 삭제된 파일을 서버에서도 삭제
823
+ if (locallyDeletedFiles.length > 0) {
824
+ console.error(`[DocuKing] 서버에서 ${locallyDeletedFiles.length}개 파일 삭제 요청`);
825
+ try {
826
+ const deleteResponse = await fetch(`${API_ENDPOINT}/files/soft-delete`, {
827
+ method: 'POST',
828
+ headers: {
829
+ 'Content-Type': 'application/json',
830
+ 'Authorization': `Bearer ${apiKey}`,
831
+ },
832
+ body: JSON.stringify({
833
+ projectId,
834
+ paths: locallyDeletedFiles,
835
+ }),
836
+ });
837
+
838
+ if (deleteResponse.ok) {
839
+ const deleteResult = await deleteResponse.json();
840
+ deleted = deleteResult.deleted || 0;
841
+ deletedFilePaths.push(...(deleteResult.deletedPaths || []));
842
+ protectedFiles = deleteResult.protected || [];
843
+
844
+ // 인덱스에서도 삭제
845
+ for (const deletedPath of locallyDeletedFiles) {
846
+ delete localIndex.files[deletedPath];
847
+ }
848
+
849
+ console.error(`[DocuKing] 서버 삭제 완료: ${deleted}개`);
850
+ if (protectedFiles.length > 0) {
851
+ console.error(`[DocuKing] 보호된 파일 (source:web): ${protectedFiles.join(', ')}`);
852
+ }
853
+ } else {
854
+ console.error(`[DocuKing] 서버 삭제 실패: ${await deleteResponse.text()}`);
855
+ }
856
+ } catch (e) {
857
+ console.error(`[DocuKing] 서버 삭제 요청 오류: ${e.message}`);
858
+ }
859
+ }
823
860
 
824
861
  // 5. 빈 폴더 생성
825
862
  let createdEmptyFolders = 0;
@@ -948,29 +985,13 @@ docuking_init을 먼저 실행하세요.`,
948
985
  }
949
986
 
950
987
  resultText += `\n\n🌐 웹 탐색기에서 커밋 히스토리를 확인할 수 있습니다: https://docuking.ai`;
951
-
952
- // Pull 결과가 있으면 앞에 추가
953
- let finalText = '';
954
- if (pullResultText) {
955
- // Pull에서 실제로 받은 파일이 있는 경우만 표시
956
- const pullHasChanges = pullResultText.includes('다운로드 (신규):') && !pullResultText.includes('다운로드 (신규): 0개');
957
- const pullHasUpdates = pullResultText.includes('업데이트 (변경됨):') && !pullResultText.includes('업데이트 (변경됨): 0개');
958
-
959
- if (pullHasChanges || pullHasUpdates) {
960
- finalText = `📥 [1단계] Pull (서버 → 로컬)\n${pullResultText}\n\n${'─'.repeat(50)}\n\n📤 [2단계] Push (로컬 → 서버)\n${resultText}`;
961
- } else {
962
- // Pull에서 변경 없으면 간단히 표시
963
- finalText = `📥 Pull: 변경 없음 (최신 상태)\n\n📤 Push:\n${resultText}`;
964
- }
965
- } else {
966
- finalText = resultText;
967
- }
988
+ resultText += `\n\n💡 서버에서 파일을 받으려면 별도로 Pull을 실행하세요.`;
968
989
 
969
990
  return {
970
991
  content: [
971
992
  {
972
993
  type: 'text',
973
- text: finalText,
994
+ text: resultText,
974
995
  },
975
996
  ],
976
997
  };
@@ -1253,9 +1274,22 @@ export async function handlePullInternal(args) {
1253
1274
  } catch (e) {
1254
1275
  // 해시 계산 실패 시 다운로드 대상
1255
1276
  }
1277
+ } else {
1278
+ // ========================================
1279
+ // 로컬에 파일이 없는 경우
1280
+ // 인덱스에 있었으면 = 로컬에서 삭제한 파일 → 스킵 (좀비 방지)
1281
+ // 인덱스에 없으면 = 서버에서 새로 생성된 파일 → 다운로드
1282
+ // ========================================
1283
+ if (localIndex.files && localIndex.files[file.path]) {
1284
+ console.error(`[DocuKing] 로컬 삭제 유지: ${file.path} (인덱스에 있었음 → 스킵, 다음 Push에서 서버 삭제됨)`);
1285
+ results.push({ type: 'skip', path: file.path, reason: 'locally-deleted' });
1286
+ skipped++;
1287
+ current++;
1288
+ continue;
1289
+ }
1256
1290
  }
1257
1291
 
1258
- // 로컬에 파일이 없는 경우만 다운로드
1292
+ // 인덱스에 없고 로컬에 없음 = 서버에서 새로 생성된 파일 → 다운로드
1259
1293
  filesToDownload.push({ ...file, fullPath });
1260
1294
  }
1261
1295
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docuking-mcp",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "DocuKing MCP Server - AI 시대의 문서 협업 플랫폼",
5
5
  "type": "module",
6
6
  "main": "index.js",