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.
- package/index.js +509 -0
- 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();
|