docuking-mcp 3.1.0 → 3.6.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/README.md CHANGED
@@ -102,15 +102,15 @@ claude mcp add docuking -s user -- docuking-mcp
102
102
  ├── xx_Urgent/ ← 긴급 보고 (동기화 대상)
103
103
  ├── xy_TalkTodoPlan/ ← 오너 AI 기록 (Talk+Todo+Plan 통합)
104
104
  ├── yy_All_Docu/ ← 문서 허브 (동기화 대상)
105
- └── yy_Coworker_{이름}/ ← 협업자 작업 폴더 (동기화 대상)
106
- └── zz_TalkTodoPlan/ ← 협업자의 AI 기록
105
+ └── zz_Coworker_{이름}/ ← 협업자 작업 폴더 (동기화 대상)
106
+ └── {이름}_TalkTodoPlan/ ← 협업자의 AI 기록
107
107
  ```
108
108
 
109
109
  **접두사 규칙:**
110
110
  - `xx_*`: 시스템/인프라 폴더
111
111
  - `xy_*`: 오너 AI 기록 폴더
112
- - `yy_*`: 사람용 폴더
113
- - `zz_*`: 협업자 AI 기록 폴더 (협업자 폴더 안에서만)
112
+ - `yy_*`: 오너 문서 폴더
113
+ - `zz_*`: 협업자 폴더
114
114
 
115
115
  ## 웹사이트
116
116
 
package/handlers/docs.js CHANGED
@@ -19,9 +19,9 @@ export async function handleTodo(args) {
19
19
 
20
20
  // 협업자 여부에 따라 AI 폴더 경로 결정
21
21
  // - 오너: localPath/xy_TalkTodoPlan/
22
- // - 협업자: localPath/yy_Coworker_{폴더명}/zz_TalkTodoPlan/
23
- const { basePath, isCoworker } = getAiBasePath(localPath);
24
- const aiFolder = isCoworker ? 'zz_TalkTodoPlan' : 'xy_TalkTodoPlan';
22
+ // - 협업자: localPath/zz_Coworker_{폴더명}/{폴더명}_TalkTodoPlan/
23
+ const { basePath, isCoworker, coworkerFolder } = getAiBasePath(localPath);
24
+ const aiFolder = isCoworker ? `${coworkerFolder}_TalkTodoPlan` : 'xy_TalkTodoPlan';
25
25
  const todoBasePath = path.join(basePath, aiFolder);
26
26
  const todoFilePath = path.join(todoBasePath, 'z_King_Todo.md');
27
27
 
@@ -211,15 +211,16 @@ ${listText}
211
211
  /**
212
212
  * docuking_talk 구현 - 대화록 자동 저장
213
213
  */
214
- export async function handleTalk(args) {
214
+ // [deprecated] docuking_talkplan으로 대체됨
215
+ async function handleTalk(args) {
215
216
  const localPath = args.localPath || process.cwd();
216
217
  const { title, content: talkContent, tags = [] } = args;
217
218
 
218
- // 협업자 여부에 따라 zz_ai 경로 결정
219
+ // 협업자 여부에 따라 AI 폴더 경로 결정
219
220
  // - 오너: localPath/xy_TalkTodoPlan/
220
- // - 협업자: localPath/yy_Coworker_{폴더명}/zz_TalkTodoPlan/
221
- const { basePath, isCoworker } = getAiBasePath(localPath);
222
- const aiFolder = isCoworker ? 'zz_TalkTodoPlan' : 'xy_TalkTodoPlan';
221
+ // - 협업자: localPath/zz_Coworker_{폴더명}/{폴더명}_TalkTodoPlan/
222
+ const { basePath, isCoworker, coworkerFolder } = getAiBasePath(localPath);
223
+ const aiFolder = isCoworker ? `${coworkerFolder}_TalkTodoPlan` : 'xy_TalkTodoPlan';
223
224
  const talkBasePath = path.join(basePath, aiFolder);
224
225
 
225
226
  // 날짜 기반 파일명 생성 (T_ 접두사)
@@ -297,15 +298,16 @@ function findPlanFilesLocal(basePath, planId) {
297
298
  /**
298
299
  * docuking_plan 구현 - 작업 계획 생성/업데이트
299
300
  */
300
- export async function handlePlan(args) {
301
+ // [deprecated] docuking_talkplan으로 대체됨
302
+ async function handlePlan(args) {
301
303
  const localPath = args.localPath || process.cwd();
302
304
  const { planId, title, goal, steps = [], notes } = args;
303
305
 
304
306
  // 협업자 여부에 따라 AI 폴더 경로 결정
305
307
  // - 오너: localPath/xy_TalkTodoPlan/
306
- // - 협업자: localPath/yy_Coworker_{폴더명}/zz_TalkTodoPlan/
307
- const { basePath, isCoworker } = getAiBasePath(localPath);
308
- const aiFolder = isCoworker ? 'zz_TalkTodoPlan' : 'xy_TalkTodoPlan';
308
+ // - 협업자: localPath/zz_Coworker_{폴더명}/{폴더명}_TalkTodoPlan/
309
+ const { basePath, isCoworker, coworkerFolder } = getAiBasePath(localPath);
310
+ const aiFolder = isCoworker ? `${coworkerFolder}_TalkTodoPlan` : 'xy_TalkTodoPlan';
309
311
  const planBasePath = path.join(basePath, aiFolder);
310
312
 
311
313
  // 기존 계획 업데이트 또는 새 계획 생성
@@ -452,15 +454,16 @@ ${notes || '(없음)'}
452
454
  /**
453
455
  * docuking_done 구현 - 작업 완료 처리
454
456
  */
455
- export async function handleDone(args) {
457
+ // [deprecated] docuking_talkplan으로 대체됨
458
+ async function handleDone(args) {
456
459
  const localPath = args.localPath || process.cwd();
457
460
  const { planId, summary, artifacts = [] } = args;
458
461
 
459
462
  // 협업자 여부에 따라 AI 폴더 경로 결정
460
463
  // - 오너: localPath/xy_TalkTodoPlan/
461
- // - 협업자: localPath/yy_Coworker_{폴더명}/zz_TalkTodoPlan/
462
- const { basePath, isCoworker } = getAiBasePath(localPath);
463
- const aiFolder = isCoworker ? 'zz_TalkTodoPlan' : 'xy_TalkTodoPlan';
464
+ // - 협업자: localPath/zz_Coworker_{폴더명}/{폴더명}_TalkTodoPlan/
465
+ const { basePath, isCoworker, coworkerFolder } = getAiBasePath(localPath);
466
+ const aiFolder = isCoworker ? `${coworkerFolder}_TalkTodoPlan` : 'xy_TalkTodoPlan';
464
467
  const planBasePath = path.join(basePath, aiFolder);
465
468
 
466
469
  // 계획 파일 찾기
@@ -564,6 +567,216 @@ ${artifacts.length > 0 ? `📦 산출물: ${artifacts.length}개` : ''}${pushMes
564
567
  };
565
568
  }
566
569
 
570
+ /**
571
+ * docuking_talkplan 구현 - 톡투플 통합 도구
572
+ *
573
+ * 흐름:
574
+ * 1. 대화 시작 → 문서 생성 (tpId 반환)
575
+ * 2. 대화 진행 → 내용 누적 (축약 없이 그대로)
576
+ * 3. 체크포인트 생기면 → 구현 계획 추가 (체크박스)
577
+ * 4. 작업 완료 → 해당 항목 완료 마킹
578
+ */
579
+ export async function handleTalkPlan(args) {
580
+ const localPath = args.localPath || process.cwd();
581
+ const { tpId, title, append, checkpoint, markDone } = args;
582
+
583
+ // 협업자 여부에 따라 AI 폴더 경로 결정
584
+ const { basePath, isCoworker, coworkerFolder } = getAiBasePath(localPath);
585
+ const aiFolder = isCoworker ? `${coworkerFolder}_TalkTodoPlan` : 'xy_TalkTodoPlan';
586
+ const talkPlanBasePath = path.join(basePath, aiFolder);
587
+
588
+ // 폴더 생성
589
+ if (!fs.existsSync(talkPlanBasePath)) {
590
+ fs.mkdirSync(talkPlanBasePath, { recursive: true });
591
+ }
592
+
593
+ // 현재 시간
594
+ const now = new Date();
595
+ 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')}`;
596
+ const timeOnly = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
597
+
598
+ let targetTpId = tpId;
599
+ let talkPlanFilePath;
600
+ let isNew = false;
601
+
602
+ // 기존 문서 찾기 또는 새로 생성
603
+ if (tpId) {
604
+ // tpId로 기존 문서 찾기
605
+ const files = findTalkPlanFiles(talkPlanBasePath, tpId);
606
+ if (files.length === 0) {
607
+ return {
608
+ content: [{
609
+ type: 'text',
610
+ text: `오류: tpId '${tpId}'에 해당하는 톡투플 문서를 찾을 수 없습니다.`,
611
+ }],
612
+ };
613
+ }
614
+ talkPlanFilePath = files[0];
615
+ } else {
616
+ // 새 문서 생성
617
+ if (!title) {
618
+ return {
619
+ content: [{
620
+ type: 'text',
621
+ text: '오류: 새 문서 생성 시 title이 필요합니다.',
622
+ }],
623
+ };
624
+ }
625
+ isNew = true;
626
+ targetTpId = generatePlanId();
627
+ const { fileName } = generateDateFileName(title, 'TP');
628
+ const fileNameWithId = fileName.replace('.md', `__${targetTpId}.md`);
629
+ talkPlanFilePath = path.join(talkPlanBasePath, fileNameWithId);
630
+ }
631
+
632
+ let content;
633
+
634
+ if (isNew) {
635
+ // 새 문서 생성
636
+ content = `# ${title}
637
+
638
+ > TalkPlan ID: \`${targetTpId}\`
639
+ > 생성: ${timestamp}
640
+ > 최종 업데이트: ${timestamp}
641
+
642
+ ---
643
+
644
+ ## 대화 기록
645
+
646
+ [${timeOnly}]
647
+ ${append || '(대화 시작)'}
648
+
649
+ ---
650
+
651
+ ## 구현 계획
652
+
653
+ (아직 없음)
654
+
655
+ ---
656
+ *이 문서는 AI 대화에서 자동 생성되었습니다.*
657
+ `;
658
+ } else {
659
+ // 기존 문서 읽기
660
+ content = fs.readFileSync(talkPlanFilePath, 'utf-8');
661
+
662
+ // 최종 업데이트 시간 갱신
663
+ content = content.replace(/> 최종 업데이트: .+/, `> 최종 업데이트: ${timestamp}`);
664
+
665
+ // 내용 누적 (append)
666
+ if (append) {
667
+ // "## 구현 계획" 바로 위에 추가
668
+ const planSectionMatch = content.match(/\n---\n\n## 구현 계획/);
669
+ if (planSectionMatch) {
670
+ const insertPos = content.indexOf(planSectionMatch[0]);
671
+ content = content.slice(0, insertPos) +
672
+ `\n[${timeOnly}]\n${append}\n` +
673
+ content.slice(insertPos);
674
+ }
675
+ }
676
+
677
+ // 체크포인트 추가 (checkpoint)
678
+ if (checkpoint) {
679
+ // 기존 체크포인트 수 세기
680
+ const checkboxPattern = /- \[[ x]\] \d+\./g;
681
+ const existingCheckboxes = content.match(checkboxPattern) || [];
682
+ const nextNum = existingCheckboxes.length + 1;
683
+
684
+ // "(아직 없음)" 제거
685
+ content = content.replace(/\(아직 없음\)\n?/, '');
686
+
687
+ // "---\n*이 문서는" 바로 위에 체크포인트 추가
688
+ const footerMatch = content.match(/\n---\n\*이 문서는/);
689
+ if (footerMatch) {
690
+ const insertPos = content.indexOf(footerMatch[0]);
691
+ content = content.slice(0, insertPos) +
692
+ `- [ ] ${nextNum}. ${checkpoint}\n` +
693
+ content.slice(insertPos);
694
+ }
695
+ }
696
+
697
+ // 완료 마킹 (markDone)
698
+ if (markDone !== undefined) {
699
+ const doneNumbers = Array.isArray(markDone) ? markDone : [markDone];
700
+ for (const num of doneNumbers) {
701
+ // "- [ ] N." 패턴을 "- [x] N." 로 변경 + 완료 시간 추가
702
+ const pattern = new RegExp(`- \\[ \\] ${num}\\.(.+?)(\n|$)`);
703
+ const match = content.match(pattern);
704
+ if (match) {
705
+ const taskText = match[1].trim();
706
+ content = content.replace(pattern, `- [x] ${num}. ${taskText} ✓${timeOnly}\n`);
707
+ }
708
+ }
709
+ }
710
+ }
711
+
712
+ // 파일 저장
713
+ fs.writeFileSync(talkPlanFilePath, content, 'utf-8');
714
+
715
+ const relativePath = path.relative(localPath, talkPlanFilePath).replace(/\\/g, '/');
716
+
717
+ // 응답 메시지 구성
718
+ let actionDesc = [];
719
+ if (isNew) actionDesc.push('문서 생성');
720
+ if (append && !isNew) actionDesc.push('대화 누적');
721
+ if (checkpoint) actionDesc.push(`체크포인트 추가`);
722
+ if (markDone !== undefined) {
723
+ const nums = Array.isArray(markDone) ? markDone : [markDone];
724
+ actionDesc.push(`#${nums.join(', #')} 완료 마킹`);
725
+ }
726
+
727
+ // 현재 미완료 체크포인트 목록 추출
728
+ const pendingPattern = /- \[ \] (\d+)\. (.+?)(\n|$)/g;
729
+ const pendingItems = [];
730
+ let match;
731
+ while ((match = pendingPattern.exec(content)) !== null) {
732
+ pendingItems.push({ num: match[1], task: match[2].trim() });
733
+ }
734
+
735
+ let pendingInfo = '';
736
+ if (pendingItems.length > 0) {
737
+ pendingInfo = `\n\n📋 미완료 항목:\n${pendingItems.map(p => ` #${p.num}: ${p.task}`).join('\n')}`;
738
+ }
739
+
740
+ return {
741
+ content: [{
742
+ type: 'text',
743
+ text: `✓ 톡투플 ${actionDesc.join(', ')}!
744
+
745
+ 🆔 TalkPlan ID: ${targetTpId}
746
+ 📁 경로: ${relativePath}
747
+ 🕐 시간: ${timestamp}${pendingInfo}
748
+
749
+ 💡 이 tpId를 기억하세요. 다음에 업데이트할 때 필요합니다.`,
750
+ }],
751
+ };
752
+ }
753
+
754
+ /**
755
+ * 톡투플 파일 찾기 (tpId로 검색)
756
+ */
757
+ function findTalkPlanFiles(basePath, tpId) {
758
+ const results = [];
759
+
760
+ if (!fs.existsSync(basePath)) {
761
+ return results;
762
+ }
763
+
764
+ function searchDir(dirPath) {
765
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
766
+ for (const entry of entries) {
767
+ const fullPath = path.join(dirPath, entry.name);
768
+ if (entry.isDirectory()) {
769
+ searchDir(fullPath);
770
+ } else if (entry.isFile() && entry.name.includes(`__${tpId}.md`)) {
771
+ results.push(fullPath);
772
+ }
773
+ }
774
+ }
775
+
776
+ searchDir(basePath);
777
+ return results;
778
+ }
779
+
567
780
  /**
568
781
  * docuking_urgent 구현 - 긴급 보고 (킹어전트)
569
782
  * 협업자가 예외 상황 발견 시 xx_Urgent/에 보고
@@ -142,7 +142,7 @@ function localizeContent(content, config) {
142
142
  }
143
143
 
144
144
  const coworkerFolder = config.coworkerFolder || '';
145
- const coworkerFolderName = `yy_Coworker_${coworkerFolder}`;
145
+ const coworkerFolderName = `zz_Coworker_${coworkerFolder}`;
146
146
 
147
147
  // 변수 치환
148
148
  let localized = content;
@@ -225,22 +225,22 @@ function updateLocalRules(localPath, currentFiles, changes, config) {
225
225
  - 프로젝트: ${projectName}
226
226
  - 역할: 협업자
227
227
  - 협업자 폴더명: ${coworkerFolder}
228
- - 작업 폴더: yy_Coworker_${coworkerFolder}/
228
+ - 작업 폴더: zz_Coworker_${coworkerFolder}/
229
229
  - 권장 브랜치: coworker/${coworkerFolder}
230
230
  - 최종 동기화: ${new Date().toISOString()}
231
231
 
232
232
  ## DocuKing 문서 작업 범위
233
233
 
234
- ※ 아래는 DocuKing 동기화 대상 폴더(yy_All_Docu/, yy_Coworker_*/, zz_ai_*/)에만 해당
234
+ ※ 아래는 DocuKing 동기화 대상 폴더(yy_All_Docu/, zz_Coworker_*/)에만 해당
235
235
  ※ 코드 파일(src/, backend/, frontend/ 등)은 자유롭게 수정 가능
236
236
 
237
237
  ### 킹푸시 가능한 영역
238
- - \`yy_Coworker_${coworkerFolder}/\` 폴더 안의 모든 파일
238
+ - \`zz_Coworker_${coworkerFolder}/\` 폴더 안의 모든 파일
239
239
 
240
240
  ### 읽기만 가능한 영역
241
241
  - \`yy_All_Docu/\` - 오너의 문서
242
- - \`yy_Coworker_*/\` - 다른 협업자의 문서
243
- - \`zz_ai_*/\` - 오너의 AI 기록
242
+ - \`zz_Coworker_*/\` - 다른 협업자의 문서
243
+ - \`xy_TalkTodoPlan/\` - 오너의 AI 기록
244
244
 
245
245
  ### 주의: 위 폴더들은 수정해도 Pull하면 원본으로 덮어씌워짐
246
246
  - 수정이 필요하면 자기 폴더에 사본을 만들거나 오너에게 연락하세요