docuking-mcp 1.0.0 → 1.2.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/index.js +617 -107
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -243,6 +243,102 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
243
243
  required: ['localPath', 'commitId'],
244
244
  },
245
245
  },
246
+ {
247
+ name: 'docuking_talk',
248
+ description: '의미 있는 대화 내용을 자동으로 z_Talk/ 폴더에 기록합니다. AI가 중요한 논의/결정이라고 판단하거나, 사용자가 "이거 기록해줘"라고 요청할 때 사용.',
249
+ inputSchema: {
250
+ type: 'object',
251
+ properties: {
252
+ localPath: {
253
+ type: 'string',
254
+ description: '로컬 프로젝트 경로',
255
+ },
256
+ title: {
257
+ type: 'string',
258
+ description: '대화록 제목 (예: "인증 방식 결정", "API 설계 논의")',
259
+ },
260
+ content: {
261
+ type: 'string',
262
+ description: '대화 내용 요약 (마크다운 형식)',
263
+ },
264
+ tags: {
265
+ type: 'array',
266
+ items: { type: 'string' },
267
+ description: '태그 목록 (예: ["설계", "결정", "API"])',
268
+ },
269
+ },
270
+ required: ['localPath', 'title', 'content'],
271
+ },
272
+ },
273
+ {
274
+ name: 'docuking_plan',
275
+ description: '작업 계획 문서를 z_PlanToResult/ 폴더에 생성하거나 업데이트합니다. 작업 시작 시 계획을 작성하고, 진행하면서 결과를 upsert합니다.',
276
+ inputSchema: {
277
+ type: 'object',
278
+ properties: {
279
+ localPath: {
280
+ type: 'string',
281
+ description: '로컬 프로젝트 경로',
282
+ },
283
+ planId: {
284
+ type: 'string',
285
+ description: '계획 ID (기존 계획 업데이트 시 사용, 생략하면 새 계획 생성)',
286
+ },
287
+ title: {
288
+ type: 'string',
289
+ description: '작업 제목 (예: "MCP 활동 로깅 구현")',
290
+ },
291
+ goal: {
292
+ type: 'string',
293
+ description: '작업 목표',
294
+ },
295
+ steps: {
296
+ type: 'array',
297
+ items: {
298
+ type: 'object',
299
+ properties: {
300
+ name: { type: 'string', description: '단계 이름' },
301
+ status: { type: 'string', enum: ['pending', 'in_progress', 'done'], description: '상태' },
302
+ result: { type: 'string', description: '결과 (완료 시)' },
303
+ },
304
+ },
305
+ description: '작업 단계 목록',
306
+ },
307
+ notes: {
308
+ type: 'string',
309
+ description: '추가 노트/메모',
310
+ },
311
+ },
312
+ required: ['localPath', 'title'],
313
+ },
314
+ },
315
+ {
316
+ name: 'docuking_done',
317
+ description: '작업 계획을 완료 상태로 변경하고 최종 결과를 기록합니다.',
318
+ inputSchema: {
319
+ type: 'object',
320
+ properties: {
321
+ localPath: {
322
+ type: 'string',
323
+ description: '로컬 프로젝트 경로',
324
+ },
325
+ planId: {
326
+ type: 'string',
327
+ description: '완료할 계획 ID',
328
+ },
329
+ summary: {
330
+ type: 'string',
331
+ description: '작업 완료 요약',
332
+ },
333
+ artifacts: {
334
+ type: 'array',
335
+ items: { type: 'string' },
336
+ description: '산출물 목록 (파일 경로, URL 등)',
337
+ },
338
+ },
339
+ required: ['localPath', 'planId', 'summary'],
340
+ },
341
+ },
246
342
  ],
247
343
  };
248
344
  });
@@ -327,6 +423,22 @@ DocuKing은 문서 버전 관리 시스템입니다. Git이 코드를 관리하
327
423
  ### 8. docuking_rollback
328
424
  특정 커밋으로 되돌립니다. (웹 탐색기에서 사용 가능)
329
425
 
426
+ ### 9. docuking_talk
427
+ 의미 있는 대화 내용을 \`z_Talk/\` 폴더에 자동 기록합니다.
428
+ - AI가 중요한 논의/결정이라고 판단할 때
429
+ - 사용자가 "이거 기록해줘"라고 요청할 때
430
+
431
+ ### 10. docuking_plan
432
+ 작업 계획을 \`z_PlanToResult/\` 폴더에 생성/업데이트합니다.
433
+ - 작업 시작 시 계획 생성
434
+ - 진행하면서 단계별 결과 upsert
435
+ - planId로 기존 계획 찾아서 업데이트
436
+
437
+ ### 11. docuking_done
438
+ 작업 계획을 완료 상태로 변경합니다.
439
+ - planId로 계획 찾기
440
+ - 완료 요약 및 산출물 기록
441
+
330
442
  ## Git과의 유사성
331
443
 
332
444
  | DocuKing | Git | 설명 |
@@ -403,7 +515,7 @@ z_DocuKing/
403
515
  **특징:**
404
516
  - 프로젝트에 초대받아 참여한 사람
405
517
  - **읽기**: 전체 문서 Pull 가능 (오너의 문서도 볼 수 있음)
406
- - **쓰기**: 자신의 폴더(\`Co-worker/{이름}/\`)에만 Push 가능
518
+ - **쓰기**: 자신의 폴더(\`zz_Coworker_{이름}/\`)에만 Push 가능
407
519
  - API Key: \`sk_cw_\`로 시작
408
520
  - 프로젝트 설정 변경 불가능
409
521
 
@@ -412,39 +524,37 @@ z_DocuKing/
412
524
  2. MCP 설정 (한 번만)
413
525
  3. 프로젝트 연결 (\`docuking_init\`)
414
526
  4. Pull로 오너의 문서 가져오기 (\`docuking_pull\`)
415
- 5. 내 폴더에 문서 작성 (\`Co-worker/{이름}/\`)
527
+ 5. 내 폴더에 문서 작성 (\`zz_Coworker_{이름}/\`)
416
528
  6. Push (\`docuking_push\`)
417
529
 
418
- **폴더 구조:**
530
+ **폴더 구조 (z_DocuKing과 zz_Coworker는 같은 레벨):**
419
531
  \`\`\`
420
- z_DocuKing/
421
- ├── 정책/
422
- │ └── README.md ← 오너의 파일 (읽기만 가능)
423
- ├── 기획/
424
- │ └── 요구사항.md ← 오너의 파일 (읽기만 가능)
425
- └── Co-worker/
426
- └── 김개발/ 참여자 "김개발"의 폴더
427
- ├── 제안서.md 여기에만 Push 가능
428
- └── 수정안.md ← 여기에만 Push 가능
532
+ 프로젝트/
533
+ ├── src/ ← 소스 코드 (git 관리)
534
+ ├── z_DocuKing/ ← 오너의 문서 공간
535
+ ├── 정책/
536
+ └── README.md ← 오너의 파일 (읽기만 가능)
537
+ └── 기획/
538
+ └── 요구사항.md 오너의 파일 (읽기만 가능)
539
+ └── zz_Coworker_김개발/ 참여자 "김개발"의 폴더 (z_DocuKing과 같은 레벨!)
540
+ ├── 제안서.md ← 여기에만 Push 가능
541
+ └── 수정안.md ← 여기에만 Push 가능
429
542
  \`\`\`
430
543
 
431
544
  **중요 규칙:**
432
- - 참여자가 오너의 폴더를 Pull해서 가지고 있어도, Push할 때는 자동으로 자신의 폴더만 필터링됨
433
- - 참여자가 다른 폴더에 Push 시도 오류 메시지 표시
434
- - \`docuking_status\`로 현재 권한 확인 가능
545
+ - 코워커 폴더(\`zz_Coworker_{이름}/\`)는 z_DocuKing과 같은 레벨에 생성됨
546
+ - 참여자는 Pull로 z_DocuKing/ 폴더의 오너 문서를 있음
547
+ - 참여자는 자신의 폴더에만 Push 가능
548
+ - \`docuking_status\`로 현재 권한과 작업 폴더 확인 가능
435
549
 
436
550
  **참여자가 오너의 파일을 수정하고 싶을 때:**
437
- 1. 오너의 파일을 Pull해서 로컬에 가져옴 (예: \`정책/README.md\`)
438
- 2. 로컬에서 수정함 (로컬 파일이므로 수정 가능)
439
- 3. Push 시도 오류 발생: "협업자는 Co-worker/{이름}/ 폴더에만 Push할 수 있습니다"
440
- 4. **해결 방법**: 수정한 내용을 자신의 폴더에 새 파일로 작성
441
- - 예: \`정책/README.md\`를 수정했다면
442
- - → \`Co-worker/김개발/정책_README_수정제안.md\`로 작성 후 Push
551
+ 1. Pull로 오너의 파일을 로컬에 가져옴 (z_DocuKing/에 저장됨)
552
+ 2. 내용을 참고하여 자신의 폴더에 수정 제안 작성
553
+ - 예: \`zz_Coworker_김개발/정책_README_수정제안.md\`로 작성 Push
443
554
 
444
555
  **AI가 참여자에게 안내해야 할 내용:**
445
- - 참여자가 Push를 요청하면, 파일이 \`Co-worker/{이름}/\` 폴더 안에 있는지 확인
446
- - 다른 폴더에 있는 파일을 Push하려고 하면, 오류 메시지를 명확히 설명하고 해결 방법 제시
447
- - 오너의 파일을 수정하고 싶다면, 자신의 폴더에 제안서 형태로 작성하도록 안내
556
+ - 참여자의 작업 폴더는 \`zz_Coworker_{이름}/\` (z_DocuKing이 아님)
557
+ - 오너의 파일을 직접 수정할 없으므로, 제안서 형태로 작성하도록 안내
448
558
 
449
559
  ## AI 응답 가이드 (중요!)
450
560
 
@@ -507,6 +617,83 @@ Push 완료! 총 10개 파일 중 3개 업로드, 6개 스킵(변경 없음), 1
507
617
  9. **재시작 최소화**: 이미 MCP가 작동 중이면 재시작 없이 바로 프로젝트 연결 진행
508
618
  10. **기존 설정 보호**: MCP 설정 시 기존 서버 설정을 덮어쓰지 말고 추가만
509
619
 
620
+ ### 대화록 자동 기록 (docuking_talk)
621
+
622
+ **언제 사용하는가:**
623
+ - 중요한 설계 결정이 내려졌을 때
624
+ - 아키텍처나 정책에 대한 논의가 있었을 때
625
+ - 사용자가 "이거 기록해줘", "이 대화 저장해줘"라고 요청할 때
626
+ - 여러 선택지 중 하나를 결정한 이유를 남겨야 할 때
627
+
628
+ **사용 예시:**
629
+ \`\`\`
630
+ 사용자: "인증은 JWT로 하자. 세션은 관리가 복잡하니까"
631
+ AI: (결정이 내려졌으므로 docuking_talk 호출)
632
+ docuking_talk({
633
+ localPath: "/current/path",
634
+ title: "인증 방식 결정 - JWT 선택",
635
+ content: "## 결정\\n인증 방식으로 JWT를 선택\\n\\n## 근거\\n- 세션 관리 복잡성 회피\\n- 무상태 아키텍처 선호",
636
+ tags: ["설계", "인증", "결정"]
637
+ })
638
+ \`\`\`
639
+
640
+ **저장 위치:** \`z_Talk/YYYY/MM/YYYY-MM-DD_HHMM__제목.md\`
641
+
642
+ ### 작업 계획 관리 (docuking_plan, docuking_done)
643
+
644
+ **언제 사용하는가:**
645
+ - 복잡한 작업을 시작할 때 (여러 단계가 필요한 작업)
646
+ - 작업 진행 상황을 기록해야 할 때
647
+ - 작업이 완료되었을 때
648
+
649
+ **작업 흐름:**
650
+ 1. 작업 시작 → \`docuking_plan\` (새 계획 생성, planId 받음)
651
+ 2. 진행 중 → \`docuking_plan\` (planId로 업데이트)
652
+ 3. 작업 완료 → \`docuking_done\` (planId로 완료 처리)
653
+
654
+ **사용 예시:**
655
+ \`\`\`
656
+ 사용자: "MCP에 talk 기능 추가해줘"
657
+ AI: (복잡한 작업이므로 docuking_plan 호출)
658
+ docuking_plan({
659
+ localPath: "/current/path",
660
+ title: "MCP talk 기능 구현",
661
+ goal: "대화 내용을 자동으로 문서화하는 기능 추가",
662
+ steps: [
663
+ { name: "도구 스키마 정의", status: "pending" },
664
+ { name: "핸들러 구현", status: "pending" },
665
+ { name: "테스트", status: "pending" }
666
+ ]
667
+ })
668
+ → planId: "abc12345" 받음
669
+
670
+ (단계 완료 시)
671
+ AI: docuking_plan({
672
+ localPath: "/current/path",
673
+ planId: "abc12345",
674
+ title: "MCP talk 기능 구현",
675
+ steps: [
676
+ { name: "도구 스키마 정의", status: "done", result: "index.js에 추가 완료" },
677
+ { name: "핸들러 구현", status: "in_progress" },
678
+ { name: "테스트", status: "pending" }
679
+ ]
680
+ })
681
+
682
+ (작업 완료 시)
683
+ AI: docuking_done({
684
+ localPath: "/current/path",
685
+ planId: "abc12345",
686
+ summary: "docuking_talk, docuking_plan, docuking_done 3개 도구 구현 완료",
687
+ artifacts: ["Docuking_mcp/index.js"]
688
+ })
689
+ \`\`\`
690
+
691
+ **저장 위치:** \`z_PlanToResult/YYYY/MM/YYYY-MM-DD_HHMM__제목__planId.md\`
692
+
693
+ **핵심 가치:**
694
+ - AI 세션이 끊겨도 (컴팩션, 세션 종료) 다음 AI가 계획 문서를 보고 이어서 작업 가능
695
+ - "어디까지 했더라?"가 아니라 "계획 문서 보고 이어서 진행"
696
+
510
697
  ### MCP 설정 관련 (AI가 처리해야 할 경우)
511
698
 
512
699
  **사용자 요청 예시:**
@@ -640,6 +827,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
640
827
  return await handleDiff(args);
641
828
  case 'docuking_rollback':
642
829
  return await handleRollback(args);
830
+ case 'docuking_talk':
831
+ return await handleTalk(args);
832
+ case 'docuking_plan':
833
+ return await handlePlan(args);
834
+ case 'docuking_done':
835
+ return await handleDone(args);
643
836
  default:
644
837
  throw new Error(`Unknown tool: ${name}`);
645
838
  }
@@ -747,24 +940,42 @@ Git처럼 무엇을 변경했는지 명확히 작성해주세요.
747
940
  const projectId = projectInfo.projectId;
748
941
  const projectName = projectInfo.projectName;
749
942
 
750
- // DocuKing 폴더 찾기
751
- const folderName = findDocuKingFolder(localPath);
752
- if (!folderName) {
753
- return {
754
- content: [
755
- {
756
- type: 'text',
757
- text: `오류: DocuKing 폴더가 없습니다.
758
- docuking_init을 먼저 실행하세요.`,
759
- },
760
- ],
761
- };
762
- }
763
- const docuKingPath = path.join(localPath, folderName);
764
-
765
943
  // Co-worker 권한은 API Key 형식에서 판단 (sk_xxx_cw_이름_)
766
944
  const coworkerMatch = API_KEY.match(/^sk_[a-f0-9]+_cw_([^_]+)_/);
767
- const allowedPathPrefix = coworkerMatch ? `Co-worker/${coworkerMatch[1]}/` : null;
945
+ const isCoworker = !!coworkerMatch;
946
+ const coworkerName = coworkerMatch ? coworkerMatch[1] : null;
947
+ const coworkerFolderName = isCoworker ? `zz_Coworker_${coworkerName}` : null;
948
+
949
+ // 작업 폴더 결정: 코워커는 zz_Coworker_{이름}/, 오너는 z_DocuKing/
950
+ let workingPath;
951
+ let serverPathPrefix = ''; // 서버에 저장될 때 경로 접두사
952
+
953
+ if (isCoworker) {
954
+ // 코워커: zz_Coworker_{이름}/ 폴더 사용 (z_DocuKing과 같은 레벨)
955
+ workingPath = path.join(localPath, coworkerFolderName);
956
+ serverPathPrefix = `${coworkerFolderName}/`;
957
+
958
+ if (!fs.existsSync(workingPath)) {
959
+ // 폴더가 없으면 생성
960
+ fs.mkdirSync(workingPath, { recursive: true });
961
+ console.log(`[DocuKing] 코워커 폴더 생성: ${coworkerFolderName}/`);
962
+ }
963
+ } else {
964
+ // 오너: z_DocuKing/ 폴더 사용
965
+ const folderName = findDocuKingFolder(localPath);
966
+ if (!folderName) {
967
+ return {
968
+ content: [
969
+ {
970
+ type: 'text',
971
+ text: `오류: DocuKing 폴더가 없습니다.
972
+ docuking_init을 먼저 실행하세요.`,
973
+ },
974
+ ],
975
+ };
976
+ }
977
+ workingPath = path.join(localPath, folderName);
978
+ }
768
979
 
769
980
  // 파일 목록 수집
770
981
  const filesToPush = [];
@@ -772,7 +983,7 @@ docuking_init을 먼저 실행하세요.`,
772
983
 
773
984
  if (filePath) {
774
985
  // 특정 파일만
775
- const fullPath = path.join(docuKingPath, filePath);
986
+ const fullPath = path.join(workingPath, filePath);
776
987
  if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
777
988
  const fileType = getFileType(filePath);
778
989
  if (fileType === 'excluded') {
@@ -786,52 +997,21 @@ docuking_init을 먼저 실행하세요.`,
786
997
  };
787
998
  }
788
999
 
789
- // 참여자인 경우 경로 체크
790
- if (allowedPathPrefix) {
791
- const normalizedPath = filePath.toLowerCase();
792
- const normalizedPrefix = allowedPathPrefix.toLowerCase();
793
- if (!normalizedPath.startsWith(normalizedPrefix)) {
794
- return {
795
- content: [
796
- {
797
- type: 'text',
798
- text: `오류: 협업자는 ${allowedPathPrefix} 폴더에만 Push할 수 있습니다.\n\n요청한 파일: ${filePath}\n허용된 경로: ${allowedPathPrefix}\n\n💡 참고: 오너의 파일을 로컬에서 수정하셨다면, 수정 내용을 ${allowedPathPrefix} 폴더에 새 파일로 작성해주세요.\n예: 정책/README.md를 수정했다면 → ${allowedPathPrefix}정책_README_수정제안.md`,
799
- },
800
- ],
801
- };
802
- }
803
- }
804
-
805
- filesToPush.push({ path: filePath, fullPath, fileType });
1000
+ // 서버 경로: 코워커는 zz_Coworker_{이름}/파일경로, 오너는 파일경로
1001
+ const serverFilePath = serverPathPrefix + filePath;
1002
+ filesToPush.push({ path: filePath, serverPath: serverFilePath, fullPath, fileType });
806
1003
  }
807
1004
  } else {
808
1005
  // 전체 파일 - 제외된 파일 목록도 수집
809
- collectFiles(docuKingPath, '', filesToPush, excludedFiles);
810
-
811
- // 참여자인 경우 허용된 경로의 파일만 필터링
812
- if (allowedPathPrefix) {
813
- const normalizedPrefix = allowedPathPrefix.toLowerCase();
814
- const filteredFiles = filesToPush.filter(file => {
815
- const normalizedPath = file.path.toLowerCase();
816
- return normalizedPath.startsWith(normalizedPrefix);
817
- });
1006
+ collectFiles(workingPath, '', filesToPush, excludedFiles);
818
1007
 
819
- if (filteredFiles.length === 0) {
820
- return {
821
- content: [
822
- {
823
- type: 'text',
824
- text: `Push할 파일이 없습니다.\n\n협업자는 ${allowedPathPrefix} 폴더에만 Push할 수 있습니다.\n\n현재 폴더 구조를 확인하고, 해당 폴더에 파일을 배치한 후 다시 시도해주세요.`,
825
- },
826
- ],
827
- };
828
- }
829
-
830
- // 필터링된 파일로 교체
831
- filesToPush.length = 0;
832
- filesToPush.push(...filteredFiles);
1008
+ // 서버 경로 추가
1009
+ for (const file of filesToPush) {
1010
+ file.serverPath = serverPathPrefix + file.path;
1011
+ }
833
1012
 
834
- console.log(`[DocuKing] 협업자 권한: ${filteredFiles.length}개 파일만 Push (${allowedPathPrefix})`);
1013
+ if (isCoworker) {
1014
+ console.log(`[DocuKing] 코워커 Push: ${filesToPush.length}개 파일 (${coworkerFolderName}/)`);
835
1015
  }
836
1016
  }
837
1017
 
@@ -940,7 +1120,7 @@ docuking_init을 먼저 실행하세요.`,
940
1120
  },
941
1121
  body: JSON.stringify({
942
1122
  projectId,
943
- path: file.path,
1123
+ path: file.serverPath, // 서버 경로 (코워커는 zz_Coworker_{이름}/파일경로)
944
1124
  content,
945
1125
  encoding, // 'utf-8' 또는 'base64'
946
1126
  message, // 커밋 메시지
@@ -1105,11 +1285,10 @@ async function handlePull(args) {
1105
1285
  const projectId = projectInfo.projectId;
1106
1286
 
1107
1287
  // DocuKing 폴더 찾기 (없으면 기본값으로 생성)
1108
- let folderName = findDocuKingFolder(localPath);
1109
- if (!folderName) {
1110
- folderName = 'z_DocuKing';
1288
+ let ownerFolderName = findDocuKingFolder(localPath);
1289
+ if (!ownerFolderName) {
1290
+ ownerFolderName = 'z_DocuKing';
1111
1291
  }
1112
- const docuKingPath = path.join(localPath, folderName);
1113
1292
 
1114
1293
  // 파일 목록 조회
1115
1294
  let files = [];
@@ -1175,7 +1354,18 @@ async function handlePull(args) {
1175
1354
  }
1176
1355
 
1177
1356
  const data = await response.json();
1178
- const fullPath = path.join(docuKingPath, file.path);
1357
+
1358
+ // 서버 경로에 따라 로컬 저장 경로 결정
1359
+ // zz_Coworker_{이름}/으로 시작하면 해당 폴더에, 아니면 z_DocuKing/에 저장
1360
+ let fullPath;
1361
+ const coworkerPrefixMatch = file.path.match(/^(zz_Coworker_[^/]+)\//);
1362
+ if (coworkerPrefixMatch) {
1363
+ // 코워커 폴더 파일: 프로젝트 루트에 zz_Coworker_{이름}/ 폴더로 저장
1364
+ fullPath = path.join(localPath, file.path);
1365
+ } else {
1366
+ // 오너 폴더 파일: z_DocuKing/ 폴더에 저장
1367
+ fullPath = path.join(localPath, ownerFolderName, file.path);
1368
+ }
1179
1369
 
1180
1370
  // 디렉토리 생성
1181
1371
  fs.mkdirSync(path.dirname(fullPath), { recursive: true });
@@ -1430,20 +1620,20 @@ async function handleStatus(args) {
1430
1620
  const coworkerMatch = API_KEY.match(/^sk_[a-f0-9]+_cw_([^_]+)_/);
1431
1621
  const isCoworker = !!coworkerMatch;
1432
1622
  const coworkerName = coworkerMatch ? coworkerMatch[1] : null;
1433
- const allowedPathPrefix = isCoworker ? `Co-worker/${coworkerName}/` : null;
1623
+ const coworkerFolderName = isCoworker ? `zz_Coworker_${coworkerName}` : null;
1434
1624
 
1435
1625
  // 권한 정보 구성
1436
1626
  let permissionInfo = '';
1437
1627
  if (isCoworker) {
1438
1628
  permissionInfo = `\n\n## 현재 권한: 참여자 (Co-worker)
1439
1629
  - 이름: ${coworkerName}
1440
- - 읽기 권한: 전체 문서 (제한 없음)
1441
- - 쓰기 권한: ${allowedPathPrefix} 폴더만
1442
- - 설명: 다른 폴더의 파일을 Pull해서 가지고 있어도, Push할 때는 자동으로 ${allowedPathPrefix} 폴더의 파일만 Push됩니다.`;
1630
+ - 읽기 권한: 전체 문서 (Pull로 z_DocuKing/ 폴더의 문서 가져오기 가능)
1631
+ - 쓰기 권한: ${coworkerFolderName}/ 폴더만 (z_DocuKing과 같은 레벨)
1632
+ - 설명: 코워커 전용 폴더에서 작업하면 자동으로 서버에 Push됩니다.`;
1443
1633
  } else {
1444
1634
  permissionInfo = `\n\n## 현재 권한: 오너 (Owner)
1445
1635
  - 읽기 권한: 전체 문서
1446
- - 쓰기 권한: 전체 문서 (제한 없음)
1636
+ - 쓰기 권한: z_DocuKing/ 폴더 전체 (제한 없음)
1447
1637
  - 설명: 프로젝트의 모든 폴더에 Push할 수 있습니다.`;
1448
1638
  }
1449
1639
 
@@ -1467,29 +1657,35 @@ async function handleStatus(args) {
1467
1657
  }
1468
1658
 
1469
1659
  // 로컬 파일 목록 조회
1470
- const folderName = findDocuKingFolder(localPath);
1471
- const docuKingPath = folderName ? path.join(localPath, folderName) : null;
1472
1660
  let localFiles = [];
1473
- if (docuKingPath && fs.existsSync(docuKingPath)) {
1474
- collectFiles(docuKingPath, '', localFiles);
1475
- }
1661
+ let pushableFiles = [];
1476
1662
 
1477
- // 참여자인 경우 Push 가능한 파일만 필터링
1478
- let pushableFiles = localFiles;
1479
- if (allowedPathPrefix) {
1480
- const normalizedPrefix = allowedPathPrefix.toLowerCase();
1481
- pushableFiles = localFiles.filter(file => {
1482
- const normalizedPath = file.path.toLowerCase();
1483
- return normalizedPath.startsWith(normalizedPrefix);
1484
- });
1663
+ if (isCoworker) {
1664
+ // 코워커: zz_Coworker_{이름}/ 폴더에서 파일 수집
1665
+ const coworkerPath = path.join(localPath, coworkerFolderName);
1666
+ if (fs.existsSync(coworkerPath)) {
1667
+ collectFiles(coworkerPath, '', localFiles);
1668
+ }
1669
+ pushableFiles = localFiles; // 코워커는 자기 폴더의 모든 파일 Push 가능
1670
+ } else {
1671
+ // 오너: z_DocuKing/ 폴더에서 파일 수집
1672
+ const folderName = findDocuKingFolder(localPath);
1673
+ const docuKingPath = folderName ? path.join(localPath, folderName) : null;
1674
+ if (docuKingPath && fs.existsSync(docuKingPath)) {
1675
+ collectFiles(docuKingPath, '', localFiles);
1676
+ }
1677
+ pushableFiles = localFiles; // 오너는 모든 파일 Push 가능
1485
1678
  }
1486
1679
 
1487
1680
  const projectNameInfo = projectName ? ` (${projectName})` : '';
1681
+ const workingFolder = isCoworker ? coworkerFolderName : 'z_DocuKing';
1488
1682
  const statusText = `DocuKing 동기화 상태
1489
1683
 
1490
1684
  **프로젝트**: ${projectId}${projectNameInfo}
1685
+ **작업 폴더**: ${workingFolder}/
1491
1686
  **로컬 파일**: ${localFiles.length}개
1492
- **서버 파일**: ${serverFiles.length}개${allowedPathPrefix ? `\n**Push 가능한 파일**: ${pushableFiles.length} (${allowedPathPrefix})` : ''}${permissionInfo}
1687
+ **서버 파일**: ${serverFiles.length}개
1688
+ **Push 가능한 파일**: ${pushableFiles.length}개${permissionInfo}
1493
1689
 
1494
1690
  ## 사용 가능한 작업
1495
1691
  - **Push**: docuking_push({ localPath, message: "..." })
@@ -1584,6 +1780,320 @@ async function handleRollback(args) {
1584
1780
  };
1585
1781
  }
1586
1782
 
1783
+ // 날짜 기반 파일명 생성 유틸
1784
+ function generateDateFileName(title) {
1785
+ const now = new Date();
1786
+ const year = now.getFullYear();
1787
+ const month = String(now.getMonth() + 1).padStart(2, '0');
1788
+ const day = String(now.getDate()).padStart(2, '0');
1789
+ const hour = String(now.getHours()).padStart(2, '0');
1790
+ const minute = String(now.getMinutes()).padStart(2, '0');
1791
+
1792
+ // 제목에서 파일명으로 사용 가능한 slug 생성
1793
+ const slug = title
1794
+ .replace(/[^\w\s가-힣-]/g, '') // 특수문자 제거
1795
+ .replace(/\s+/g, '_') // 공백을 언더스코어로
1796
+ .substring(0, 50); // 50자 제한
1797
+
1798
+ return {
1799
+ fileName: `${year}-${month}-${day}_${hour}${minute}__${slug}.md`,
1800
+ yearMonth: `${year}/${month}`,
1801
+ timestamp: `${year}-${month}-${day} ${hour}:${minute}`,
1802
+ };
1803
+ }
1804
+
1805
+ // 계획 ID 생성 (간단한 UUID-like)
1806
+ function generatePlanId() {
1807
+ const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
1808
+ let id = '';
1809
+ for (let i = 0; i < 8; i++) {
1810
+ id += chars.charAt(Math.floor(Math.random() * chars.length));
1811
+ }
1812
+ return id;
1813
+ }
1814
+
1815
+ // docuking_talk 구현 - 대화록 자동 저장
1816
+ async function handleTalk(args) {
1817
+ const { localPath, title, content, tags = [] } = args;
1818
+
1819
+ // z_Talk 폴더 경로
1820
+ const talkBasePath = path.join(localPath, 'z_Talk');
1821
+
1822
+ // 날짜 기반 파일명 생성
1823
+ const { fileName, yearMonth, timestamp } = generateDateFileName(title);
1824
+ const talkFolderPath = path.join(talkBasePath, yearMonth);
1825
+ const talkFilePath = path.join(talkFolderPath, fileName);
1826
+
1827
+ // 폴더 생성
1828
+ if (!fs.existsSync(talkFolderPath)) {
1829
+ fs.mkdirSync(talkFolderPath, { recursive: true });
1830
+ }
1831
+
1832
+ // 태그 문자열
1833
+ const tagString = tags.length > 0 ? `\n태그: ${tags.map(t => `#${t}`).join(' ')}` : '';
1834
+
1835
+ // 마크다운 문서 생성
1836
+ const document = `# ${title}
1837
+
1838
+ > 기록 시간: ${timestamp}${tagString}
1839
+
1840
+ ---
1841
+
1842
+ ${content}
1843
+
1844
+ ---
1845
+ *이 문서는 AI와의 대화에서 자동 생성되었습니다.*
1846
+ `;
1847
+
1848
+ // 파일 저장
1849
+ fs.writeFileSync(talkFilePath, document, 'utf-8');
1850
+
1851
+ const relativePath = path.relative(localPath, talkFilePath).replace(/\\/g, '/');
1852
+
1853
+ return {
1854
+ content: [
1855
+ {
1856
+ type: 'text',
1857
+ text: `✓ 대화록 저장 완료!
1858
+
1859
+ 📝 제목: ${title}
1860
+ 📁 경로: ${relativePath}
1861
+ 🕐 시간: ${timestamp}${tags.length > 0 ? `\n🏷️ 태그: ${tags.join(', ')}` : ''}
1862
+
1863
+ 💡 DocuKing에 Push하면 웹에서도 확인할 수 있습니다.`,
1864
+ },
1865
+ ],
1866
+ };
1867
+ }
1868
+
1869
+ // docuking_plan 구현 - 작업 계획 생성/업데이트
1870
+ async function handlePlan(args) {
1871
+ const { localPath, planId, title, goal, steps = [], notes } = args;
1872
+
1873
+ // z_PlanToResult 폴더 경로
1874
+ const planBasePath = path.join(localPath, 'z_PlanToResult');
1875
+
1876
+ // 기존 계획 업데이트 또는 새 계획 생성
1877
+ let targetPlanId = planId;
1878
+ let isNew = !planId;
1879
+ let planFilePath;
1880
+ let existingContent = null;
1881
+
1882
+ if (planId) {
1883
+ // 기존 계획 찾기
1884
+ const planFiles = findPlanFiles(planBasePath, planId);
1885
+ if (planFiles.length === 0) {
1886
+ return {
1887
+ content: [
1888
+ {
1889
+ type: 'text',
1890
+ text: `오류: planId '${planId}'에 해당하는 계획을 찾을 수 없습니다.`,
1891
+ },
1892
+ ],
1893
+ };
1894
+ }
1895
+ planFilePath = planFiles[0];
1896
+ existingContent = fs.readFileSync(planFilePath, 'utf-8');
1897
+ } else {
1898
+ // 새 계획 생성
1899
+ targetPlanId = generatePlanId();
1900
+ const { fileName, yearMonth, timestamp } = generateDateFileName(title);
1901
+ const planFolderPath = path.join(planBasePath, yearMonth);
1902
+
1903
+ if (!fs.existsSync(planFolderPath)) {
1904
+ fs.mkdirSync(planFolderPath, { recursive: true });
1905
+ }
1906
+
1907
+ // 파일명에 planId 포함
1908
+ const fileNameWithId = fileName.replace('.md', `__${targetPlanId}.md`);
1909
+ planFilePath = path.join(planFolderPath, fileNameWithId);
1910
+ }
1911
+
1912
+ // 현재 시간
1913
+ const now = new Date();
1914
+ const timestamp = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
1915
+
1916
+ // 단계 상태 문자열 생성
1917
+ const stepsMarkdown = steps.length > 0
1918
+ ? steps.map((step, i) => {
1919
+ const statusIcon = step.status === 'done' ? '✅' : step.status === 'in_progress' ? '🔄' : '⬜';
1920
+ const resultText = step.result ? ` → ${step.result}` : '';
1921
+ return `${i + 1}. ${statusIcon} ${step.name}${resultText}`;
1922
+ }).join('\n')
1923
+ : '(단계 미정의)';
1924
+
1925
+ // 진행률 계산
1926
+ const doneCount = steps.filter(s => s.status === 'done').length;
1927
+ const totalCount = steps.length;
1928
+ const progress = totalCount > 0 ? Math.round((doneCount / totalCount) * 100) : 0;
1929
+
1930
+ // 마크다운 문서 생성
1931
+ let document;
1932
+ if (isNew) {
1933
+ document = `# ${title}
1934
+
1935
+ > Plan ID: \`${targetPlanId}\`
1936
+ > 생성: ${timestamp}
1937
+ > 상태: 진행 중 (${progress}%)
1938
+
1939
+ ---
1940
+
1941
+ ## 목표
1942
+ ${goal || '(목표 미정의)'}
1943
+
1944
+ ## 진행 단계
1945
+ ${stepsMarkdown}
1946
+
1947
+ ## 노트
1948
+ ${notes || '(없음)'}
1949
+
1950
+ ---
1951
+
1952
+ ## 진행 기록
1953
+ - ${timestamp}: 계획 생성
1954
+
1955
+ ---
1956
+ *이 문서는 AI 작업 계획에서 자동 생성되었습니다.*
1957
+ `;
1958
+ } else {
1959
+ // 기존 문서 업데이트 (진행 기록에 추가)
1960
+ const updateEntry = `- ${timestamp}: 계획 업데이트 (진행률 ${progress}%)`;
1961
+
1962
+ // 기존 내용에서 섹션 업데이트
1963
+ document = existingContent
1964
+ .replace(/> 상태: .+/, `> 상태: 진행 중 (${progress}%)`)
1965
+ .replace(/## 진행 단계\n[\s\S]*?(?=\n## )/, `## 진행 단계\n${stepsMarkdown}\n\n`)
1966
+ .replace(/## 진행 기록\n/, `## 진행 기록\n${updateEntry}\n`);
1967
+
1968
+ if (notes) {
1969
+ document = document.replace(/## 노트\n[\s\S]*?(?=\n---\n\n## 진행 기록)/, `## 노트\n${notes}\n\n`);
1970
+ }
1971
+ }
1972
+
1973
+ // 파일 저장
1974
+ fs.writeFileSync(planFilePath, document, 'utf-8');
1975
+
1976
+ const relativePath = path.relative(localPath, planFilePath).replace(/\\/g, '/');
1977
+
1978
+ return {
1979
+ content: [
1980
+ {
1981
+ type: 'text',
1982
+ text: `✓ 작업 계획 ${isNew ? '생성' : '업데이트'} 완료!
1983
+
1984
+ 📋 제목: ${title}
1985
+ 🆔 Plan ID: ${targetPlanId}
1986
+ 📁 경로: ${relativePath}
1987
+ 📊 진행률: ${progress}% (${doneCount}/${totalCount})
1988
+
1989
+ 💡 이 planId를 기억해두세요. 나중에 업데이트하거나 완료 처리할 때 필요합니다.
1990
+ 💡 DocuKing에 Push하면 웹에서도 확인할 수 있습니다.`,
1991
+ },
1992
+ ],
1993
+ };
1994
+ }
1995
+
1996
+ // 계획 파일 찾기 유틸
1997
+ function findPlanFiles(basePath, planId) {
1998
+ const results = [];
1999
+
2000
+ if (!fs.existsSync(basePath)) {
2001
+ return results;
2002
+ }
2003
+
2004
+ function searchDir(dirPath) {
2005
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
2006
+ for (const entry of entries) {
2007
+ const fullPath = path.join(dirPath, entry.name);
2008
+ if (entry.isDirectory()) {
2009
+ searchDir(fullPath);
2010
+ } else if (entry.isFile() && entry.name.includes(`__${planId}.md`)) {
2011
+ results.push(fullPath);
2012
+ }
2013
+ }
2014
+ }
2015
+
2016
+ searchDir(basePath);
2017
+ return results;
2018
+ }
2019
+
2020
+ // docuking_done 구현 - 작업 완료 처리
2021
+ async function handleDone(args) {
2022
+ const { localPath, planId, summary, artifacts = [] } = args;
2023
+
2024
+ // z_PlanToResult 폴더 경로
2025
+ const planBasePath = path.join(localPath, 'z_PlanToResult');
2026
+
2027
+ // 계획 파일 찾기
2028
+ const planFiles = findPlanFiles(planBasePath, planId);
2029
+ if (planFiles.length === 0) {
2030
+ return {
2031
+ content: [
2032
+ {
2033
+ type: 'text',
2034
+ text: `오류: planId '${planId}'에 해당하는 계획을 찾을 수 없습니다.`,
2035
+ },
2036
+ ],
2037
+ };
2038
+ }
2039
+
2040
+ const planFilePath = planFiles[0];
2041
+ let content = fs.readFileSync(planFilePath, 'utf-8');
2042
+
2043
+ // 현재 시간
2044
+ const now = new Date();
2045
+ const timestamp = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
2046
+
2047
+ // 산출물 목록
2048
+ const artifactsMarkdown = artifacts.length > 0
2049
+ ? artifacts.map(a => `- ${a}`).join('\n')
2050
+ : '(없음)';
2051
+
2052
+ // 완료 섹션 추가
2053
+ const completionSection = `
2054
+
2055
+ ## ✅ 완료
2056
+ > 완료 시간: ${timestamp}
2057
+
2058
+ ### 요약
2059
+ ${summary}
2060
+
2061
+ ### 산출물
2062
+ ${artifactsMarkdown}
2063
+ `;
2064
+
2065
+ // 상태 업데이트 및 완료 섹션 추가
2066
+ content = content
2067
+ .replace(/> 상태: .+/, `> 상태: ✅ 완료`)
2068
+ .replace(/---\n\*이 문서는 AI 작업 계획에서 자동 생성되었습니다.\*/, `${completionSection}\n---\n*이 문서는 AI 작업 계획에서 자동 생성되었습니다.*`);
2069
+
2070
+ // 진행 기록에 완료 추가
2071
+ content = content.replace(/## 진행 기록\n/, `## 진행 기록\n- ${timestamp}: ✅ 작업 완료\n`);
2072
+
2073
+ // 파일 저장
2074
+ fs.writeFileSync(planFilePath, content, 'utf-8');
2075
+
2076
+ const relativePath = path.relative(localPath, planFilePath).replace(/\\/g, '/');
2077
+
2078
+ return {
2079
+ content: [
2080
+ {
2081
+ type: 'text',
2082
+ text: `✅ 작업 완료 처리됨!
2083
+
2084
+ 🆔 Plan ID: ${planId}
2085
+ 📁 경로: ${relativePath}
2086
+ 🕐 완료 시간: ${timestamp}
2087
+
2088
+ 📝 요약: ${summary}
2089
+ ${artifacts.length > 0 ? `📦 산출물: ${artifacts.length}개` : ''}
2090
+
2091
+ 💡 DocuKing에 Push하면 웹에서 완료된 작업을 확인할 수 있습니다.`,
2092
+ },
2093
+ ],
2094
+ };
2095
+ }
2096
+
1587
2097
  // 서버 시작
1588
2098
  async function main() {
1589
2099
  const transport = new StdioServerTransport();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docuking-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "DocuKing MCP Server - AI 시대의 문서 협업 플랫폼",
5
5
  "type": "module",
6
6
  "main": "index.js",