docuking-mcp 1.9.0 → 1.9.2
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 +115 -8
- 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
|
|
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
|
-
|
|
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
|
-
|
|
1384
|
-
|
|
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
|
}
|
|
@@ -1864,6 +1963,9 @@ function findDocuKingFolder(projectPath) {
|
|
|
1864
1963
|
|
|
1865
1964
|
// 유틸: 디렉토리 재귀 탐색
|
|
1866
1965
|
// excludedFiles: 제외된 파일 목록을 수집할 배열 (선택)
|
|
1966
|
+
// AI 작업 기록 폴더 (공유 제외 대상)
|
|
1967
|
+
const AI_WORK_FOLDERS = ['z_Talk', 'z_Todo', 'z_King_Todo', 'zz_Plan_Result'];
|
|
1968
|
+
|
|
1867
1969
|
function collectFiles(basePath, relativePath, results, excludedFiles = null) {
|
|
1868
1970
|
const fullPath = path.join(basePath, relativePath);
|
|
1869
1971
|
const entries = fs.readdirSync(fullPath, { withFileTypes: true });
|
|
@@ -1872,6 +1974,11 @@ function collectFiles(basePath, relativePath, results, excludedFiles = null) {
|
|
|
1872
1974
|
const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
1873
1975
|
|
|
1874
1976
|
if (entry.isDirectory()) {
|
|
1977
|
+
// AI 작업 기록 폴더는 제외 (z_Talk, z_Todo, z_King_Todo, zz_Plan_Result)
|
|
1978
|
+
if (AI_WORK_FOLDERS.includes(entry.name)) {
|
|
1979
|
+
console.error(`[DocuKing] 제외됨: ${entryRelPath}/ (AI 작업 기록 폴더 - 개인용)`);
|
|
1980
|
+
continue;
|
|
1981
|
+
}
|
|
1875
1982
|
collectFiles(basePath, entryRelPath, results, excludedFiles);
|
|
1876
1983
|
} else if (entry.isFile()) {
|
|
1877
1984
|
const fileType = getFileType(entry.name);
|