docuking-mcp 1.0.0 → 1.1.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 +509 -0
  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 | 설명 |
@@ -507,6 +619,83 @@ Push 완료! 총 10개 파일 중 3개 업로드, 6개 스킵(변경 없음), 1
507
619
  9. **재시작 최소화**: 이미 MCP가 작동 중이면 재시작 없이 바로 프로젝트 연결 진행
508
620
  10. **기존 설정 보호**: MCP 설정 시 기존 서버 설정을 덮어쓰지 말고 추가만
509
621
 
622
+ ### 대화록 자동 기록 (docuking_talk)
623
+
624
+ **언제 사용하는가:**
625
+ - 중요한 설계 결정이 내려졌을 때
626
+ - 아키텍처나 정책에 대한 논의가 있었을 때
627
+ - 사용자가 "이거 기록해줘", "이 대화 저장해줘"라고 요청할 때
628
+ - 여러 선택지 중 하나를 결정한 이유를 남겨야 할 때
629
+
630
+ **사용 예시:**
631
+ \`\`\`
632
+ 사용자: "인증은 JWT로 하자. 세션은 관리가 복잡하니까"
633
+ AI: (결정이 내려졌으므로 docuking_talk 호출)
634
+ docuking_talk({
635
+ localPath: "/current/path",
636
+ title: "인증 방식 결정 - JWT 선택",
637
+ content: "## 결정\\n인증 방식으로 JWT를 선택\\n\\n## 근거\\n- 세션 관리 복잡성 회피\\n- 무상태 아키텍처 선호",
638
+ tags: ["설계", "인증", "결정"]
639
+ })
640
+ \`\`\`
641
+
642
+ **저장 위치:** \`z_Talk/YYYY/MM/YYYY-MM-DD_HHMM__제목.md\`
643
+
644
+ ### 작업 계획 관리 (docuking_plan, docuking_done)
645
+
646
+ **언제 사용하는가:**
647
+ - 복잡한 작업을 시작할 때 (여러 단계가 필요한 작업)
648
+ - 작업 진행 상황을 기록해야 할 때
649
+ - 작업이 완료되었을 때
650
+
651
+ **작업 흐름:**
652
+ 1. 작업 시작 → \`docuking_plan\` (새 계획 생성, planId 받음)
653
+ 2. 진행 중 → \`docuking_plan\` (planId로 업데이트)
654
+ 3. 작업 완료 → \`docuking_done\` (planId로 완료 처리)
655
+
656
+ **사용 예시:**
657
+ \`\`\`
658
+ 사용자: "MCP에 talk 기능 추가해줘"
659
+ AI: (복잡한 작업이므로 docuking_plan 호출)
660
+ docuking_plan({
661
+ localPath: "/current/path",
662
+ title: "MCP talk 기능 구현",
663
+ goal: "대화 내용을 자동으로 문서화하는 기능 추가",
664
+ steps: [
665
+ { name: "도구 스키마 정의", status: "pending" },
666
+ { name: "핸들러 구현", status: "pending" },
667
+ { name: "테스트", status: "pending" }
668
+ ]
669
+ })
670
+ → planId: "abc12345" 받음
671
+
672
+ (단계 완료 시)
673
+ AI: docuking_plan({
674
+ localPath: "/current/path",
675
+ planId: "abc12345",
676
+ title: "MCP talk 기능 구현",
677
+ steps: [
678
+ { name: "도구 스키마 정의", status: "done", result: "index.js에 추가 완료" },
679
+ { name: "핸들러 구현", status: "in_progress" },
680
+ { name: "테스트", status: "pending" }
681
+ ]
682
+ })
683
+
684
+ (작업 완료 시)
685
+ AI: docuking_done({
686
+ localPath: "/current/path",
687
+ planId: "abc12345",
688
+ summary: "docuking_talk, docuking_plan, docuking_done 3개 도구 구현 완료",
689
+ artifacts: ["Docuking_mcp/index.js"]
690
+ })
691
+ \`\`\`
692
+
693
+ **저장 위치:** \`z_PlanToResult/YYYY/MM/YYYY-MM-DD_HHMM__제목__planId.md\`
694
+
695
+ **핵심 가치:**
696
+ - AI 세션이 끊겨도 (컴팩션, 세션 종료) 다음 AI가 계획 문서를 보고 이어서 작업 가능
697
+ - "어디까지 했더라?"가 아니라 "계획 문서 보고 이어서 진행"
698
+
510
699
  ### MCP 설정 관련 (AI가 처리해야 할 경우)
511
700
 
512
701
  **사용자 요청 예시:**
@@ -640,6 +829,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
640
829
  return await handleDiff(args);
641
830
  case 'docuking_rollback':
642
831
  return await handleRollback(args);
832
+ case 'docuking_talk':
833
+ return await handleTalk(args);
834
+ case 'docuking_plan':
835
+ return await handlePlan(args);
836
+ case 'docuking_done':
837
+ return await handleDone(args);
643
838
  default:
644
839
  throw new Error(`Unknown tool: ${name}`);
645
840
  }
@@ -1584,6 +1779,320 @@ async function handleRollback(args) {
1584
1779
  };
1585
1780
  }
1586
1781
 
1782
+ // 날짜 기반 파일명 생성 유틸
1783
+ function generateDateFileName(title) {
1784
+ const now = new Date();
1785
+ const year = now.getFullYear();
1786
+ const month = String(now.getMonth() + 1).padStart(2, '0');
1787
+ const day = String(now.getDate()).padStart(2, '0');
1788
+ const hour = String(now.getHours()).padStart(2, '0');
1789
+ const minute = String(now.getMinutes()).padStart(2, '0');
1790
+
1791
+ // 제목에서 파일명으로 사용 가능한 slug 생성
1792
+ const slug = title
1793
+ .replace(/[^\w\s가-힣-]/g, '') // 특수문자 제거
1794
+ .replace(/\s+/g, '_') // 공백을 언더스코어로
1795
+ .substring(0, 50); // 50자 제한
1796
+
1797
+ return {
1798
+ fileName: `${year}-${month}-${day}_${hour}${minute}__${slug}.md`,
1799
+ yearMonth: `${year}/${month}`,
1800
+ timestamp: `${year}-${month}-${day} ${hour}:${minute}`,
1801
+ };
1802
+ }
1803
+
1804
+ // 계획 ID 생성 (간단한 UUID-like)
1805
+ function generatePlanId() {
1806
+ const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
1807
+ let id = '';
1808
+ for (let i = 0; i < 8; i++) {
1809
+ id += chars.charAt(Math.floor(Math.random() * chars.length));
1810
+ }
1811
+ return id;
1812
+ }
1813
+
1814
+ // docuking_talk 구현 - 대화록 자동 저장
1815
+ async function handleTalk(args) {
1816
+ const { localPath, title, content, tags = [] } = args;
1817
+
1818
+ // z_Talk 폴더 경로
1819
+ const talkBasePath = path.join(localPath, 'z_Talk');
1820
+
1821
+ // 날짜 기반 파일명 생성
1822
+ const { fileName, yearMonth, timestamp } = generateDateFileName(title);
1823
+ const talkFolderPath = path.join(talkBasePath, yearMonth);
1824
+ const talkFilePath = path.join(talkFolderPath, fileName);
1825
+
1826
+ // 폴더 생성
1827
+ if (!fs.existsSync(talkFolderPath)) {
1828
+ fs.mkdirSync(talkFolderPath, { recursive: true });
1829
+ }
1830
+
1831
+ // 태그 문자열
1832
+ const tagString = tags.length > 0 ? `\n태그: ${tags.map(t => `#${t}`).join(' ')}` : '';
1833
+
1834
+ // 마크다운 문서 생성
1835
+ const document = `# ${title}
1836
+
1837
+ > 기록 시간: ${timestamp}${tagString}
1838
+
1839
+ ---
1840
+
1841
+ ${content}
1842
+
1843
+ ---
1844
+ *이 문서는 AI와의 대화에서 자동 생성되었습니다.*
1845
+ `;
1846
+
1847
+ // 파일 저장
1848
+ fs.writeFileSync(talkFilePath, document, 'utf-8');
1849
+
1850
+ const relativePath = path.relative(localPath, talkFilePath).replace(/\\/g, '/');
1851
+
1852
+ return {
1853
+ content: [
1854
+ {
1855
+ type: 'text',
1856
+ text: `✓ 대화록 저장 완료!
1857
+
1858
+ 📝 제목: ${title}
1859
+ 📁 경로: ${relativePath}
1860
+ 🕐 시간: ${timestamp}${tags.length > 0 ? `\n🏷️ 태그: ${tags.join(', ')}` : ''}
1861
+
1862
+ 💡 DocuKing에 Push하면 웹에서도 확인할 수 있습니다.`,
1863
+ },
1864
+ ],
1865
+ };
1866
+ }
1867
+
1868
+ // docuking_plan 구현 - 작업 계획 생성/업데이트
1869
+ async function handlePlan(args) {
1870
+ const { localPath, planId, title, goal, steps = [], notes } = args;
1871
+
1872
+ // z_PlanToResult 폴더 경로
1873
+ const planBasePath = path.join(localPath, 'z_PlanToResult');
1874
+
1875
+ // 기존 계획 업데이트 또는 새 계획 생성
1876
+ let targetPlanId = planId;
1877
+ let isNew = !planId;
1878
+ let planFilePath;
1879
+ let existingContent = null;
1880
+
1881
+ if (planId) {
1882
+ // 기존 계획 찾기
1883
+ const planFiles = findPlanFiles(planBasePath, planId);
1884
+ if (planFiles.length === 0) {
1885
+ return {
1886
+ content: [
1887
+ {
1888
+ type: 'text',
1889
+ text: `오류: planId '${planId}'에 해당하는 계획을 찾을 수 없습니다.`,
1890
+ },
1891
+ ],
1892
+ };
1893
+ }
1894
+ planFilePath = planFiles[0];
1895
+ existingContent = fs.readFileSync(planFilePath, 'utf-8');
1896
+ } else {
1897
+ // 새 계획 생성
1898
+ targetPlanId = generatePlanId();
1899
+ const { fileName, yearMonth, timestamp } = generateDateFileName(title);
1900
+ const planFolderPath = path.join(planBasePath, yearMonth);
1901
+
1902
+ if (!fs.existsSync(planFolderPath)) {
1903
+ fs.mkdirSync(planFolderPath, { recursive: true });
1904
+ }
1905
+
1906
+ // 파일명에 planId 포함
1907
+ const fileNameWithId = fileName.replace('.md', `__${targetPlanId}.md`);
1908
+ planFilePath = path.join(planFolderPath, fileNameWithId);
1909
+ }
1910
+
1911
+ // 현재 시간
1912
+ const now = new Date();
1913
+ 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')}`;
1914
+
1915
+ // 단계 상태 문자열 생성
1916
+ const stepsMarkdown = steps.length > 0
1917
+ ? steps.map((step, i) => {
1918
+ const statusIcon = step.status === 'done' ? '✅' : step.status === 'in_progress' ? '🔄' : '⬜';
1919
+ const resultText = step.result ? ` → ${step.result}` : '';
1920
+ return `${i + 1}. ${statusIcon} ${step.name}${resultText}`;
1921
+ }).join('\n')
1922
+ : '(단계 미정의)';
1923
+
1924
+ // 진행률 계산
1925
+ const doneCount = steps.filter(s => s.status === 'done').length;
1926
+ const totalCount = steps.length;
1927
+ const progress = totalCount > 0 ? Math.round((doneCount / totalCount) * 100) : 0;
1928
+
1929
+ // 마크다운 문서 생성
1930
+ let document;
1931
+ if (isNew) {
1932
+ document = `# ${title}
1933
+
1934
+ > Plan ID: \`${targetPlanId}\`
1935
+ > 생성: ${timestamp}
1936
+ > 상태: 진행 중 (${progress}%)
1937
+
1938
+ ---
1939
+
1940
+ ## 목표
1941
+ ${goal || '(목표 미정의)'}
1942
+
1943
+ ## 진행 단계
1944
+ ${stepsMarkdown}
1945
+
1946
+ ## 노트
1947
+ ${notes || '(없음)'}
1948
+
1949
+ ---
1950
+
1951
+ ## 진행 기록
1952
+ - ${timestamp}: 계획 생성
1953
+
1954
+ ---
1955
+ *이 문서는 AI 작업 계획에서 자동 생성되었습니다.*
1956
+ `;
1957
+ } else {
1958
+ // 기존 문서 업데이트 (진행 기록에 추가)
1959
+ const updateEntry = `- ${timestamp}: 계획 업데이트 (진행률 ${progress}%)`;
1960
+
1961
+ // 기존 내용에서 섹션 업데이트
1962
+ document = existingContent
1963
+ .replace(/> 상태: .+/, `> 상태: 진행 중 (${progress}%)`)
1964
+ .replace(/## 진행 단계\n[\s\S]*?(?=\n## )/, `## 진행 단계\n${stepsMarkdown}\n\n`)
1965
+ .replace(/## 진행 기록\n/, `## 진행 기록\n${updateEntry}\n`);
1966
+
1967
+ if (notes) {
1968
+ document = document.replace(/## 노트\n[\s\S]*?(?=\n---\n\n## 진행 기록)/, `## 노트\n${notes}\n\n`);
1969
+ }
1970
+ }
1971
+
1972
+ // 파일 저장
1973
+ fs.writeFileSync(planFilePath, document, 'utf-8');
1974
+
1975
+ const relativePath = path.relative(localPath, planFilePath).replace(/\\/g, '/');
1976
+
1977
+ return {
1978
+ content: [
1979
+ {
1980
+ type: 'text',
1981
+ text: `✓ 작업 계획 ${isNew ? '생성' : '업데이트'} 완료!
1982
+
1983
+ 📋 제목: ${title}
1984
+ 🆔 Plan ID: ${targetPlanId}
1985
+ 📁 경로: ${relativePath}
1986
+ 📊 진행률: ${progress}% (${doneCount}/${totalCount})
1987
+
1988
+ 💡 이 planId를 기억해두세요. 나중에 업데이트하거나 완료 처리할 때 필요합니다.
1989
+ 💡 DocuKing에 Push하면 웹에서도 확인할 수 있습니다.`,
1990
+ },
1991
+ ],
1992
+ };
1993
+ }
1994
+
1995
+ // 계획 파일 찾기 유틸
1996
+ function findPlanFiles(basePath, planId) {
1997
+ const results = [];
1998
+
1999
+ if (!fs.existsSync(basePath)) {
2000
+ return results;
2001
+ }
2002
+
2003
+ function searchDir(dirPath) {
2004
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
2005
+ for (const entry of entries) {
2006
+ const fullPath = path.join(dirPath, entry.name);
2007
+ if (entry.isDirectory()) {
2008
+ searchDir(fullPath);
2009
+ } else if (entry.isFile() && entry.name.includes(`__${planId}.md`)) {
2010
+ results.push(fullPath);
2011
+ }
2012
+ }
2013
+ }
2014
+
2015
+ searchDir(basePath);
2016
+ return results;
2017
+ }
2018
+
2019
+ // docuking_done 구현 - 작업 완료 처리
2020
+ async function handleDone(args) {
2021
+ const { localPath, planId, summary, artifacts = [] } = args;
2022
+
2023
+ // z_PlanToResult 폴더 경로
2024
+ const planBasePath = path.join(localPath, 'z_PlanToResult');
2025
+
2026
+ // 계획 파일 찾기
2027
+ const planFiles = findPlanFiles(planBasePath, planId);
2028
+ if (planFiles.length === 0) {
2029
+ return {
2030
+ content: [
2031
+ {
2032
+ type: 'text',
2033
+ text: `오류: planId '${planId}'에 해당하는 계획을 찾을 수 없습니다.`,
2034
+ },
2035
+ ],
2036
+ };
2037
+ }
2038
+
2039
+ const planFilePath = planFiles[0];
2040
+ let content = fs.readFileSync(planFilePath, 'utf-8');
2041
+
2042
+ // 현재 시간
2043
+ const now = new Date();
2044
+ 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')}`;
2045
+
2046
+ // 산출물 목록
2047
+ const artifactsMarkdown = artifacts.length > 0
2048
+ ? artifacts.map(a => `- ${a}`).join('\n')
2049
+ : '(없음)';
2050
+
2051
+ // 완료 섹션 추가
2052
+ const completionSection = `
2053
+
2054
+ ## ✅ 완료
2055
+ > 완료 시간: ${timestamp}
2056
+
2057
+ ### 요약
2058
+ ${summary}
2059
+
2060
+ ### 산출물
2061
+ ${artifactsMarkdown}
2062
+ `;
2063
+
2064
+ // 상태 업데이트 및 완료 섹션 추가
2065
+ content = content
2066
+ .replace(/> 상태: .+/, `> 상태: ✅ 완료`)
2067
+ .replace(/---\n\*이 문서는 AI 작업 계획에서 자동 생성되었습니다.\*/, `${completionSection}\n---\n*이 문서는 AI 작업 계획에서 자동 생성되었습니다.*`);
2068
+
2069
+ // 진행 기록에 완료 추가
2070
+ content = content.replace(/## 진행 기록\n/, `## 진행 기록\n- ${timestamp}: ✅ 작업 완료\n`);
2071
+
2072
+ // 파일 저장
2073
+ fs.writeFileSync(planFilePath, content, 'utf-8');
2074
+
2075
+ const relativePath = path.relative(localPath, planFilePath).replace(/\\/g, '/');
2076
+
2077
+ return {
2078
+ content: [
2079
+ {
2080
+ type: 'text',
2081
+ text: `✅ 작업 완료 처리됨!
2082
+
2083
+ 🆔 Plan ID: ${planId}
2084
+ 📁 경로: ${relativePath}
2085
+ 🕐 완료 시간: ${timestamp}
2086
+
2087
+ 📝 요약: ${summary}
2088
+ ${artifacts.length > 0 ? `📦 산출물: ${artifacts.length}개` : ''}
2089
+
2090
+ 💡 DocuKing에 Push하면 웹에서 완료된 작업을 확인할 수 있습니다.`,
2091
+ },
2092
+ ],
2093
+ };
2094
+ }
2095
+
1587
2096
  // 서버 시작
1588
2097
  async function main() {
1589
2098
  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.1.0",
4
4
  "description": "DocuKing MCP Server - AI 시대의 문서 협업 플랫폼",
5
5
  "type": "module",
6
6
  "main": "index.js",