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.
- package/handlers/sync.js +89 -55
- package/package.json +1 -1
package/handlers/sync.js
CHANGED
|
@@ -482,33 +482,13 @@ docuking_init을 먼저 실행하세요.`,
|
|
|
482
482
|
}
|
|
483
483
|
|
|
484
484
|
// ========================================
|
|
485
|
-
//
|
|
486
|
-
// soft-delete에서 "로컬에 없는 파일" 판단에 사용
|
|
487
|
-
// Pull이 파일을 내려받아도 이 목록은 변하지 않음
|
|
485
|
+
// Pull 선행 제거 (2026-01-24)
|
|
488
486
|
// ========================================
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
// ========================================
|
|
493
|
-
// Pull 실행 (git pull && git push 패턴)
|
|
487
|
+
// 이전: Push 전에 Pull 실행 (git pull && git push 패턴)
|
|
488
|
+
// 문제: Pull이 로컬 변경사항(삭제, 수정)을 덮어씀 → 좀비 파일 부활
|
|
489
|
+
// 해결: Push는 Push만. Pull은 사용자가 별도로 호출.
|
|
494
490
|
// ========================================
|
|
495
|
-
|
|
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.
|
|
786
|
+
// 4. 인덱스 기반 삭제 전파 (2026-01-24 재구현)
|
|
807
787
|
// ========================================
|
|
808
|
-
// 이전
|
|
809
|
-
//
|
|
810
|
-
//
|
|
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
|
-
|
|
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:
|
|
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
|
|