docuking-mcp 3.1.0 → 3.6.1
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 +4 -4
- package/handlers/docs.js +229 -16
- package/handlers/kingcast.js +6 -6
- package/handlers/sync.js +165 -57
- package/index.js +239 -139
- package/lib/config.js +4 -2
- package/lib/index-cache.js +393 -17
- package/lib/init.js +23 -21
- package/lib/utils.js +1 -1
- package/package.json +1 -1
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
|
-
└──
|
|
106
|
-
└──
|
|
105
|
+
└── zz_Coworker_{이름}/ ← 협업자 작업 폴더 (동기화 대상)
|
|
106
|
+
└── {이름}_TalkTodoPlan/ ← 협업자의 AI 기록
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
**접두사 규칙:**
|
|
110
110
|
- `xx_*`: 시스템/인프라 폴더
|
|
111
111
|
- `xy_*`: 오너 AI 기록 폴더
|
|
112
|
-
- `yy_*`:
|
|
113
|
-
- `zz_*`: 협업자
|
|
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/
|
|
23
|
-
const { basePath, isCoworker } = getAiBasePath(localPath);
|
|
24
|
-
const aiFolder = isCoworker ?
|
|
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
|
-
|
|
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
|
-
// 협업자 여부에 따라
|
|
219
|
+
// 협업자 여부에 따라 AI 폴더 경로 결정
|
|
219
220
|
// - 오너: localPath/xy_TalkTodoPlan/
|
|
220
|
-
// - 협업자: localPath/
|
|
221
|
-
const { basePath, isCoworker } = getAiBasePath(localPath);
|
|
222
|
-
const aiFolder = isCoworker ?
|
|
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
|
-
|
|
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/
|
|
307
|
-
const { basePath, isCoworker } = getAiBasePath(localPath);
|
|
308
|
-
const aiFolder = isCoworker ?
|
|
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
|
-
|
|
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/
|
|
462
|
-
const { basePath, isCoworker } = getAiBasePath(localPath);
|
|
463
|
-
const aiFolder = isCoworker ?
|
|
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/에 보고
|
package/handlers/kingcast.js
CHANGED
|
@@ -142,7 +142,7 @@ function localizeContent(content, config) {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
const coworkerFolder = config.coworkerFolder || '';
|
|
145
|
-
const coworkerFolderName = `
|
|
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
|
-
- 작업 폴더:
|
|
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/,
|
|
234
|
+
※ 아래는 DocuKing 동기화 대상 폴더(yy_All_Docu/, zz_Coworker_*/)에만 해당
|
|
235
235
|
※ 코드 파일(src/, backend/, frontend/ 등)은 자유롭게 수정 가능
|
|
236
236
|
|
|
237
237
|
### 킹푸시 가능한 영역
|
|
238
|
-
- \`
|
|
238
|
+
- \`zz_Coworker_${coworkerFolder}/\` 폴더 안의 모든 파일
|
|
239
239
|
|
|
240
240
|
### 읽기만 가능한 영역
|
|
241
241
|
- \`yy_All_Docu/\` - 오너의 문서
|
|
242
|
-
- \`
|
|
243
|
-
- \`
|
|
242
|
+
- \`zz_Coworker_*/\` - 다른 협업자의 문서
|
|
243
|
+
- \`xy_TalkTodoPlan/\` - 오너의 AI 기록
|
|
244
244
|
|
|
245
245
|
### 주의: 위 폴더들은 수정해도 Pull하면 원본으로 덮어씌워짐
|
|
246
246
|
- 수정이 필요하면 자기 폴더에 사본을 만들거나 오너에게 연락하세요
|