docuking-mcp 1.2.3 → 1.2.5

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 +150 -69
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1082,16 +1082,47 @@ docuking_init을 먼저 실행하세요.`,
1082
1082
  filesToPush.push({ path: filePath, serverPath: serverFilePath, fullPath, fileType });
1083
1083
  }
1084
1084
  } else {
1085
- // 전체 파일 - 제외된 파일 목록도 수집
1086
- collectFiles(workingPath, '', filesToPush, excludedFiles);
1087
-
1088
- // 서버 경로 추가
1089
- for (const file of filesToPush) {
1090
- file.serverPath = serverPathPrefix + file.path;
1091
- }
1092
-
1093
1085
  if (isCoworker) {
1086
+ // 코워커: 본인 폴더만 수집
1087
+ collectFiles(workingPath, '', filesToPush, excludedFiles);
1088
+
1089
+ // 서버 경로 추가
1090
+ for (const file of filesToPush) {
1091
+ file.serverPath = serverPathPrefix + file.path;
1092
+ }
1094
1093
  console.log(`[DocuKing] 코워커 Push: ${filesToPush.length}개 파일 (${coworkerFolderName}/)`);
1094
+ } else {
1095
+ // 오너: z_DocuKing/ + 모든 zz_Coworker_*/ 폴더 수집
1096
+ const docuKingFolderName = findDocuKingFolder(localPath);
1097
+
1098
+ // 1. z_DocuKing/ 폴더 수집
1099
+ if (docuKingFolderName) {
1100
+ const docuKingPath = path.join(localPath, docuKingFolderName);
1101
+ const docuKingFiles = [];
1102
+ collectFiles(docuKingPath, '', docuKingFiles, excludedFiles);
1103
+
1104
+ for (const file of docuKingFiles) {
1105
+ file.serverPath = file.path; // 오너 폴더는 서버 경로 = 파일 경로
1106
+ filesToPush.push(file);
1107
+ }
1108
+ }
1109
+
1110
+ // 2. 모든 zz_Coworker_*/ 폴더 수집
1111
+ const coworkerFolders = findCoworkerFolders(localPath);
1112
+ for (const cwFolder of coworkerFolders) {
1113
+ const cwPath = path.join(localPath, cwFolder);
1114
+ const cwFiles = [];
1115
+ collectFiles(cwPath, '', cwFiles, excludedFiles);
1116
+
1117
+ for (const file of cwFiles) {
1118
+ file.serverPath = `${cwFolder}/${file.path}`; // 코워커 폴더명 포함
1119
+ file.path = `${cwFolder}/${file.path}`; // 로컬 경로도 폴더명 포함
1120
+ file.fullPath = path.join(cwPath, file.path.replace(`${cwFolder}/`, ''));
1121
+ filesToPush.push(file);
1122
+ }
1123
+ }
1124
+
1125
+ console.log(`[DocuKing] 오너 Push: ${filesToPush.length}개 파일 (z_DocuKing/ + ${coworkerFolders.length}개 코워커 폴더)`);
1095
1126
  }
1096
1127
  }
1097
1128
 
@@ -1106,30 +1137,6 @@ docuking_init을 먼저 실행하세요.`,
1106
1137
  };
1107
1138
  }
1108
1139
 
1109
- // 파일 업로드 (진행률 표시)
1110
- const results = [];
1111
- const total = filesToPush.length;
1112
- let current = 0;
1113
- let skipped = 0;
1114
-
1115
- // 시작 안내 메시지 출력 (AI가 사용자에게 전달할 수 있도록)
1116
- console.error(`[DocuKing] Push 시작: ${total}개 파일`);
1117
- console.error(`[DocuKing] 💡 실시간 진행상황은 DocuKing 웹(https://docuking.ai)에서 확인하세요`);
1118
-
1119
- // Sync 시작 알림 (웹에서 프로그레스바 표시용)
1120
- try {
1121
- await fetch(`${API_ENDPOINT}/projects/${projectId}/sync/start`, {
1122
- method: 'POST',
1123
- headers: {
1124
- 'Content-Type': 'application/json',
1125
- 'Authorization': `Bearer ${API_KEY}`,
1126
- },
1127
- body: JSON.stringify({ totalFiles: total }),
1128
- });
1129
- } catch (e) {
1130
- console.error('[DocuKing] Sync 시작 알림 실패:', e.message);
1131
- }
1132
-
1133
1140
  // 서버에서 파일 해시 조회 (변경 감지용)
1134
1141
  let serverFileHashes = {};
1135
1142
  try {
@@ -1150,36 +1157,82 @@ docuking_init을 먼저 실행하세요.`,
1150
1157
  console.error('[DocuKing] 파일 해시 조회 실패:', e.message);
1151
1158
  }
1152
1159
 
1160
+ // ★ 변경된 파일만 필터링 (업로드 전에 미리 계산)
1161
+ const changedFiles = [];
1162
+ const unchangedFiles = [];
1163
+
1153
1164
  for (const file of filesToPush) {
1165
+ let fileHash;
1166
+ let content;
1167
+ let encoding = 'utf-8';
1168
+
1169
+ if (file.fileType === 'binary') {
1170
+ const buffer = fs.readFileSync(file.fullPath);
1171
+ fileHash = crypto.createHash('sha256').update(buffer).digest('hex');
1172
+ content = buffer.toString('base64');
1173
+ encoding = 'base64';
1174
+ } else {
1175
+ content = fs.readFileSync(file.fullPath, 'utf-8');
1176
+ fileHash = crypto.createHash('sha256').update(content, 'utf-8').digest('hex');
1177
+ }
1178
+
1179
+ if (serverFileHashes[file.path] === fileHash) {
1180
+ unchangedFiles.push(file.path);
1181
+ } else {
1182
+ changedFiles.push({ ...file, content, encoding, fileHash });
1183
+ }
1184
+ }
1185
+
1186
+ const totalLocal = filesToPush.length;
1187
+ const totalChanged = changedFiles.length;
1188
+ const totalUnchanged = unchangedFiles.length;
1189
+
1190
+ // ★ 변경된 파일이 없으면 즉시 반환
1191
+ if (totalChanged === 0) {
1192
+ return {
1193
+ content: [
1194
+ {
1195
+ type: 'text',
1196
+ text: `변경된 파일이 없습니다.
1197
+
1198
+ 📊 로컬 파일: ${totalLocal}개
1199
+ ✓ 서버와 동일: ${totalUnchanged}개
1200
+ ⊘ 변경 필요: 0개
1201
+
1202
+ 이미 모든 파일이 서버와 동기화되어 있습니다.`,
1203
+ },
1204
+ ],
1205
+ };
1206
+ }
1207
+
1208
+ // ★ 변경된 파일만 업로드 (AI에게 명확히 알림)
1209
+ console.error(`[DocuKing] 변경 감지 완료: 전체 ${totalLocal}개 중 ${totalChanged}개만 업로드`);
1210
+ console.error(`[DocuKing] 💡 실시간 진행상황은 DocuKing 웹(https://docuking.ai)에서 확인하세요`);
1211
+
1212
+ // Sync 시작 알림 (변경된 파일 수만 전달)
1213
+ try {
1214
+ await fetch(`${API_ENDPOINT}/projects/${projectId}/sync/start`, {
1215
+ method: 'POST',
1216
+ headers: {
1217
+ 'Content-Type': 'application/json',
1218
+ 'Authorization': `Bearer ${API_KEY}`,
1219
+ },
1220
+ body: JSON.stringify({ totalFiles: totalChanged }),
1221
+ });
1222
+ } catch (e) {
1223
+ console.error('[DocuKing] Sync 시작 알림 실패:', e.message);
1224
+ }
1225
+
1226
+ // 파일 업로드 (변경된 파일만)
1227
+ const results = [];
1228
+ let current = 0;
1229
+
1230
+ for (const file of changedFiles) {
1154
1231
  current++;
1155
- const progress = `${current}/${total}`;
1232
+ const progress = `${current}/${totalChanged}`;
1156
1233
 
1157
1234
  try {
1158
- // 파일 해시 계산 (변경 감지)
1159
- let fileHash;
1160
- let content;
1161
- let encoding = 'utf-8';
1162
-
1163
- if (file.fileType === 'binary') {
1164
- // 바이너리 파일은 Base64로 인코딩
1165
- const buffer = fs.readFileSync(file.fullPath);
1166
- fileHash = crypto.createHash('sha256').update(buffer).digest('hex');
1167
- content = buffer.toString('base64');
1168
- encoding = 'base64';
1169
- } else {
1170
- // 텍스트 파일은 UTF-8
1171
- content = fs.readFileSync(file.fullPath, 'utf-8');
1172
- fileHash = crypto.createHash('sha256').update(content, 'utf-8').digest('hex');
1173
- }
1174
-
1175
- // 변경 감지: 서버에 같은 해시가 있으면 스킵
1176
- if (serverFileHashes[file.path] === fileHash) {
1177
- const resultText = `${progress} ⊘ ${file.path} (변경 없음)`;
1178
- results.push(resultText);
1179
- console.log(resultText);
1180
- skipped++;
1181
- continue;
1182
- }
1235
+ const { content, encoding, fileHash } = file;
1183
1236
 
1184
1237
  // 재시도 로직 (최대 3회)
1185
1238
  let lastError = null;
@@ -1294,17 +1347,16 @@ docuking_init을 먼저 실행하세요.`,
1294
1347
 
1295
1348
  const successCount = results.filter(r => r.includes('✓')).length;
1296
1349
  const failCount = results.filter(r => r.includes('✗')).length;
1297
- const skippedCount = skipped; // 이미 계산된 스킵 개수 사용
1298
1350
  const excludedCount = excludedFiles.length;
1299
1351
 
1300
- // 요약 정보
1301
- let summary = `\n📦 커밋 메시지: "${message}"\n\n📊 처리 결과:\n - 파일: ${total}개\n - 업로드: ${successCount}개\n - 스킵 (변경 없음): ${skippedCount}개\n - 실패: ${failCount}개`;
1352
+ // 요약 정보 (★ 변경된 파일만 업로드했음을 명확히 표시)
1353
+ let summary = `\n📦 커밋 메시지: "${message}"\n\n📊 처리 결과:\n - 로컬 파일: ${totalLocal}개\n - 변경 없음 (스킵): ${totalUnchanged}개\n - 변경 감지: ${totalChanged}개\n - 업로드 성공: ${successCount}개\n - 업로드 실패: ${failCount}개`;
1302
1354
  if (excludedCount > 0) {
1303
1355
  summary += `\n - 제외 (압축/설치파일): ${excludedCount}개`;
1304
1356
  }
1305
1357
 
1306
1358
  // 상세 결과를 표시 (Git처럼)
1307
- let resultText = `✓ Push 완료!${summary}`;
1359
+ let resultText = `✓ Push 완료! (변경된 ${totalChanged}개 파일만 업로드)${summary}`;
1308
1360
 
1309
1361
  // 업로드된 파일이 있으면 상세 목록 표시
1310
1362
  if (successCount > 0) {
@@ -1312,12 +1364,6 @@ docuking_init을 먼저 실행하세요.`,
1312
1364
  resultText += `\n\n📤 업로드된 파일 (${successCount}개):\n${uploadedFiles.map(r => ` ${r.replace(/^\d+\/\d+ /, '')}`).join('\n')}`;
1313
1365
  }
1314
1366
 
1315
- // 스킵된 파일이 있으면 표시
1316
- if (skippedCount > 0) {
1317
- const skippedFiles = results.filter(r => r.includes('⊘'));
1318
- resultText += `\n\n⏭️ 스킵된 파일 (${skippedCount}개, 변경 없음):\n${skippedFiles.map(r => ` ${r.replace(/^\d+\/\d+ /, '')}`).join('\n')}`;
1319
- }
1320
-
1321
1367
  // 실패한 파일이 있으면 표시
1322
1368
  if (failCount > 0) {
1323
1369
  const failedFiles = results.filter(r => r.includes('✗'));
@@ -1612,10 +1658,29 @@ function getFileType(fileName) {
1612
1658
  return 'binary';
1613
1659
  }
1614
1660
 
1615
- // 유틸: DocuKing 폴더 찾기 (docuking 포함, 대소문자 무관)
1661
+ // 유틸: DocuKing 폴더 찾기
1662
+ // 우선순위: z_DocuKing > Z_DocuKing > 기타 docuking 포함 폴더
1616
1663
  function findDocuKingFolder(projectPath) {
1617
1664
  try {
1618
1665
  const entries = fs.readdirSync(projectPath, { withFileTypes: true });
1666
+
1667
+ // 1순위: z_DocuKing 또는 Z_DocuKing (정확한 이름)
1668
+ for (const entry of entries) {
1669
+ if (entry.isDirectory() && (entry.name === 'z_DocuKing' || entry.name === 'Z_DocuKing')) {
1670
+ return entry.name;
1671
+ }
1672
+ }
1673
+
1674
+ // 2순위: z_ 또는 Z_로 시작하고 docuking 포함
1675
+ for (const entry of entries) {
1676
+ if (entry.isDirectory() &&
1677
+ (entry.name.startsWith('z_') || entry.name.startsWith('Z_')) &&
1678
+ entry.name.toLowerCase().includes('docuking')) {
1679
+ return entry.name;
1680
+ }
1681
+ }
1682
+
1683
+ // 3순위: docuking 포함하는 아무 폴더 (하위 호환)
1619
1684
  for (const entry of entries) {
1620
1685
  if (entry.isDirectory() && entry.name.toLowerCase().includes('docuking')) {
1621
1686
  return entry.name;
@@ -1627,6 +1692,22 @@ function findDocuKingFolder(projectPath) {
1627
1692
  return null;
1628
1693
  }
1629
1694
 
1695
+ // 유틸: 모든 zz_Coworker_* 폴더 찾기
1696
+ function findCoworkerFolders(projectPath) {
1697
+ const folders = [];
1698
+ try {
1699
+ const entries = fs.readdirSync(projectPath, { withFileTypes: true });
1700
+ for (const entry of entries) {
1701
+ if (entry.isDirectory() && entry.name.startsWith('zz_Coworker_')) {
1702
+ folders.push(entry.name);
1703
+ }
1704
+ }
1705
+ } catch (e) {
1706
+ // 디렉토리 읽기 실패
1707
+ }
1708
+ return folders;
1709
+ }
1710
+
1630
1711
  // 유틸: 디렉토리 재귀 탐색
1631
1712
  // excludedFiles: 제외된 파일 목록을 수집할 배열 (선택)
1632
1713
  function collectFiles(basePath, relativePath, results, excludedFiles = null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docuking-mcp",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "DocuKing MCP Server - AI 시대의 문서 협업 플랫폼",
5
5
  "type": "module",
6
6
  "main": "index.js",