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.
- package/index.js +617 -107
- 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
|
-
- **쓰기**: 자신의 폴더(\`
|
|
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. 내 폴더에 문서 작성 (\`
|
|
527
|
+
5. 내 폴더에 문서 작성 (\`zz_Coworker_{이름}/\`)
|
|
416
528
|
6. Push (\`docuking_push\`)
|
|
417
529
|
|
|
418
|
-
**폴더
|
|
530
|
+
**폴더 구조 (z_DocuKing과 zz_Coworker는 같은 레벨):**
|
|
419
531
|
\`\`\`
|
|
420
|
-
|
|
421
|
-
├──
|
|
422
|
-
|
|
423
|
-
├──
|
|
424
|
-
│ └──
|
|
425
|
-
└──
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
-
-
|
|
433
|
-
-
|
|
434
|
-
-
|
|
545
|
+
- 코워커 폴더(\`zz_Coworker_{이름}/\`)는 z_DocuKing과 같은 레벨에 생성됨
|
|
546
|
+
- 참여자는 Pull로 z_DocuKing/ 폴더의 오너 문서를 볼 수 있음
|
|
547
|
+
- 참여자는 자신의 폴더에만 Push 가능
|
|
548
|
+
- \`docuking_status\`로 현재 권한과 작업 폴더 확인 가능
|
|
435
549
|
|
|
436
550
|
**참여자가 오너의 파일을 수정하고 싶을 때:**
|
|
437
|
-
1. 오너의 파일을
|
|
438
|
-
2.
|
|
439
|
-
|
|
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
|
-
-
|
|
446
|
-
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
791
|
-
|
|
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(
|
|
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
|
-
|
|
820
|
-
|
|
821
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
1109
|
-
if (!
|
|
1110
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
- 쓰기 권한: ${
|
|
1442
|
-
- 설명:
|
|
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
|
-
|
|
1474
|
-
collectFiles(docuKingPath, '', localFiles);
|
|
1475
|
-
}
|
|
1661
|
+
let pushableFiles = [];
|
|
1476
1662
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
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}
|
|
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();
|