docuking-mcp 2.5.7 → 2.7.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/handlers/docs.js +519 -0
- package/handlers/index.js +34 -0
- package/handlers/kingcast.js +569 -0
- package/handlers/sync.js +1501 -0
- package/handlers/validate.js +544 -0
- package/handlers/version.js +102 -0
- package/index.js +149 -2762
- package/lib/config.js +114 -0
- package/lib/files.js +212 -0
- package/lib/index.js +41 -0
- package/lib/init.js +270 -0
- package/lib/utils.js +74 -0
- package/package.json +1 -1
package/handlers/docs.js
ADDED
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocuKing MCP - 문서 핸들러 모듈
|
|
3
|
+
* todo, talk, plan, done
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
|
|
9
|
+
import { getAiBasePath } from '../lib/config.js';
|
|
10
|
+
import { generateDateFileName, generatePlanId, findPlanFiles } from '../lib/utils.js';
|
|
11
|
+
import { handlePush } from './sync.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* docuking_todo 구현 - 킹투두 (단일 파일 누적)
|
|
15
|
+
*/
|
|
16
|
+
export async function handleTodo(args) {
|
|
17
|
+
const localPath = args.localPath || process.cwd();
|
|
18
|
+
const { action, todo, todoId } = args;
|
|
19
|
+
|
|
20
|
+
// 협업자 여부에 따라 zz_ai 경로 결정
|
|
21
|
+
// - 오너: localPath/zz_ai_2_Todo/
|
|
22
|
+
// - 협업자: localPath/yy_Coworker_{폴더명}/zz_ai_2_Todo/
|
|
23
|
+
const { basePath } = getAiBasePath(localPath);
|
|
24
|
+
const todoBasePath = path.join(basePath, 'zz_ai_2_Todo');
|
|
25
|
+
const todoFilePath = path.join(todoBasePath, 'z_King_Todo.md');
|
|
26
|
+
|
|
27
|
+
// 폴더 생성
|
|
28
|
+
if (!fs.existsSync(todoBasePath)) {
|
|
29
|
+
fs.mkdirSync(todoBasePath, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 파일이 없으면 헤더와 함께 생성
|
|
33
|
+
if (!fs.existsSync(todoFilePath)) {
|
|
34
|
+
const header = `# TODO 목록
|
|
35
|
+
|
|
36
|
+
> 날짜 1개 = 등록일 (진행중) / 날짜 2개 = 등록일/완료일 (완료)
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
`;
|
|
41
|
+
fs.writeFileSync(todoFilePath, header, 'utf-8');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 파일 읽기
|
|
45
|
+
let content = fs.readFileSync(todoFilePath, 'utf-8');
|
|
46
|
+
|
|
47
|
+
// 현재 TODO 번호 찾기 (가장 큰 번호)
|
|
48
|
+
const todoPattern = /^(\d+)\. /gm;
|
|
49
|
+
let maxId = 0;
|
|
50
|
+
let match;
|
|
51
|
+
while ((match = todoPattern.exec(content)) !== null) {
|
|
52
|
+
const id = parseInt(match[1], 10);
|
|
53
|
+
if (id > maxId) maxId = id;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const now = new Date();
|
|
57
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
58
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
59
|
+
const dateStr = `${month}.${day}`;
|
|
60
|
+
|
|
61
|
+
if (action === 'add') {
|
|
62
|
+
if (!todo) {
|
|
63
|
+
return {
|
|
64
|
+
content: [{ type: 'text', text: '오류: todo 파라미터가 필요합니다.' }],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const newId = maxId + 1;
|
|
69
|
+
|
|
70
|
+
// todo에서 태그와 키워드, 설명 분리
|
|
71
|
+
// 형식: "[태그] 키워드 - 설명" 또는 "[태그] 키워드"
|
|
72
|
+
let tag = '';
|
|
73
|
+
let keyword = '';
|
|
74
|
+
let description = '';
|
|
75
|
+
|
|
76
|
+
const tagMatch = todo.match(/^\[([^\]]+)\]\s*/);
|
|
77
|
+
if (tagMatch) {
|
|
78
|
+
tag = tagMatch[1];
|
|
79
|
+
const rest = todo.slice(tagMatch[0].length);
|
|
80
|
+
const descSplit = rest.split(/\s*[-–]\s*/);
|
|
81
|
+
keyword = descSplit[0].trim();
|
|
82
|
+
description = descSplit.slice(1).join(' - ').trim();
|
|
83
|
+
} else {
|
|
84
|
+
// 태그 없이 입력된 경우
|
|
85
|
+
const descSplit = todo.split(/\s*[-–]\s*/);
|
|
86
|
+
keyword = descSplit[0].trim();
|
|
87
|
+
description = descSplit.slice(1).join(' - ').trim();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 킹투두 형식으로 작성
|
|
91
|
+
let newTodo;
|
|
92
|
+
if (tag) {
|
|
93
|
+
newTodo = `${newId}. ⚙️ **[${tag}] ${keyword}** ${dateStr}\n`;
|
|
94
|
+
} else {
|
|
95
|
+
newTodo = `${newId}. ⚙️ **${keyword}** ${dateStr}\n`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (description) {
|
|
99
|
+
newTodo += ` ${description}\n`;
|
|
100
|
+
}
|
|
101
|
+
newTodo += '\n';
|
|
102
|
+
|
|
103
|
+
// 파일 끝에 추가
|
|
104
|
+
fs.appendFileSync(todoFilePath, newTodo, 'utf-8');
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
content: [{
|
|
108
|
+
type: 'text',
|
|
109
|
+
text: `✓ 킹투두에 등록했습니다!
|
|
110
|
+
|
|
111
|
+
📝 #${newId}: ${tag ? `[${tag}] ` : ''}${keyword}${description ? ` - ${description}` : ''}
|
|
112
|
+
📅 등록: ${dateStr}
|
|
113
|
+
|
|
114
|
+
💡 완료 시: docuking_todo(action: "done", todoId: ${newId})`,
|
|
115
|
+
}],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (action === 'done') {
|
|
120
|
+
if (!todoId) {
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: 'text', text: '오류: todoId 파라미터가 필요합니다.' }],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 해당 번호의 TODO 찾아서 ⚙️ -> ✅ 변경 및 날짜 추가
|
|
127
|
+
const todoLinePattern = new RegExp(`^(${todoId}\\. )⚙️( \\*\\*.*\\*\\* )(\\d+\\.\\d+)(.*)$`, 'm');
|
|
128
|
+
const matched = content.match(todoLinePattern);
|
|
129
|
+
|
|
130
|
+
if (!matched) {
|
|
131
|
+
// 이미 완료된 항목인지 확인
|
|
132
|
+
const completedPattern = new RegExp(`^${todoId}\\. ✅`, 'm');
|
|
133
|
+
if (completedPattern.test(content)) {
|
|
134
|
+
return {
|
|
135
|
+
content: [{ type: 'text', text: `킹투두 #${todoId}는 이미 완료 상태입니다.` }],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
content: [{ type: 'text', text: `오류: 킹투두 #${todoId}를 찾을 수 없습니다.` }],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ⚙️ -> ✅ 변경 + 완료 날짜 추가 (등록일/완료일)
|
|
144
|
+
const updatedContent = content.replace(
|
|
145
|
+
todoLinePattern,
|
|
146
|
+
`$1✅$2$3/${dateStr}$4`
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
fs.writeFileSync(todoFilePath, updatedContent, 'utf-8');
|
|
150
|
+
|
|
151
|
+
// 완료된 TODO 내용 추출
|
|
152
|
+
const keywordMatch = matched[2].match(/\*\*(.+)\*\*/);
|
|
153
|
+
const todoKeyword = keywordMatch ? keywordMatch[1] : '';
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
content: [{
|
|
157
|
+
type: 'text',
|
|
158
|
+
text: `✓ 킹투두 #${todoId} 완료!
|
|
159
|
+
|
|
160
|
+
✅ ${todoKeyword}
|
|
161
|
+
📅 완료: ${dateStr}`,
|
|
162
|
+
}],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (action === 'list') {
|
|
167
|
+
// 미완료(⚙️) TODO 추출
|
|
168
|
+
const pendingPattern = /^(\d+)\. ⚙️ \*\*(.+)\*\* (\d+\.\d+)/gm;
|
|
169
|
+
const pendingTodos = [];
|
|
170
|
+
let listMatch;
|
|
171
|
+
while ((listMatch = pendingPattern.exec(content)) !== null) {
|
|
172
|
+
pendingTodos.push({ id: listMatch[1], keyword: listMatch[2], date: listMatch[3] });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 완료된(✅) TODO 수 세기
|
|
176
|
+
const completedCount = (content.match(/^(\d+)\. ✅/gm) || []).length;
|
|
177
|
+
|
|
178
|
+
if (pendingTodos.length === 0) {
|
|
179
|
+
return {
|
|
180
|
+
content: [{
|
|
181
|
+
type: 'text',
|
|
182
|
+
text: `📋 킹투두 미결: 없음
|
|
183
|
+
|
|
184
|
+
✅ 완료: ${completedCount}개
|
|
185
|
+
📁 전체 기록: zz_ai_2_Todo/z_King_Todo.md`,
|
|
186
|
+
}],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const listText = pendingTodos.map(t => ` #${t.id}: ${t.keyword} (${t.date})`).join('\n');
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
content: [{
|
|
194
|
+
type: 'text',
|
|
195
|
+
text: `📋 킹투두 미결: ${pendingTodos.length}개
|
|
196
|
+
|
|
197
|
+
${listText}
|
|
198
|
+
|
|
199
|
+
✅ 완료: ${completedCount}개
|
|
200
|
+
📁 전체 기록: zz_ai_2_Todo/z_King_Todo.md`,
|
|
201
|
+
}],
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
content: [{ type: 'text', text: '오류: action은 add, done, list 중 하나여야 합니다.' }],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* docuking_talk 구현 - 대화록 자동 저장
|
|
212
|
+
*/
|
|
213
|
+
export async function handleTalk(args) {
|
|
214
|
+
const localPath = args.localPath || process.cwd();
|
|
215
|
+
const { title, content: talkContent, tags = [] } = args;
|
|
216
|
+
|
|
217
|
+
// 협업자 여부에 따라 zz_ai 경로 결정
|
|
218
|
+
// - 오너: localPath/zz_ai_1_Talk/
|
|
219
|
+
// - 협업자: localPath/yy_Coworker_{폴더명}/zz_ai_1_Talk/
|
|
220
|
+
const { basePath } = getAiBasePath(localPath);
|
|
221
|
+
const talkBasePath = path.join(basePath, 'zz_ai_1_Talk');
|
|
222
|
+
|
|
223
|
+
// 날짜 기반 파일명 생성 (T_ 접두사)
|
|
224
|
+
const { fileName, timestamp } = generateDateFileName(title, 'T');
|
|
225
|
+
const talkFilePath = path.join(talkBasePath, fileName);
|
|
226
|
+
|
|
227
|
+
// 폴더 생성
|
|
228
|
+
if (!fs.existsSync(talkBasePath)) {
|
|
229
|
+
fs.mkdirSync(talkBasePath, { recursive: true });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 태그 문자열
|
|
233
|
+
const tagString = tags.length > 0 ? `\n태그: ${tags.map(t => `#${t}`).join(' ')}` : '';
|
|
234
|
+
|
|
235
|
+
// 마크다운 문서 생성
|
|
236
|
+
const document = `# ${title}
|
|
237
|
+
|
|
238
|
+
> 기록 시간: ${timestamp}${tagString}
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
${talkContent}
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
*이 문서는 AI와의 대화에서 자동 생성되었습니다.*
|
|
246
|
+
`;
|
|
247
|
+
|
|
248
|
+
// 파일 저장
|
|
249
|
+
fs.writeFileSync(talkFilePath, document, 'utf-8');
|
|
250
|
+
|
|
251
|
+
const relativePath = path.relative(localPath, talkFilePath).replace(/\\/g, '/');
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
content: [
|
|
255
|
+
{
|
|
256
|
+
type: 'text',
|
|
257
|
+
text: `✓ 대화록 저장 완료!
|
|
258
|
+
|
|
259
|
+
📝 제목: ${title}
|
|
260
|
+
📁 경로: ${relativePath}
|
|
261
|
+
🕐 시간: ${timestamp}${tags.length > 0 ? `\n🏷️ 태그: ${tags.join(', ')}` : ''}
|
|
262
|
+
|
|
263
|
+
💡 DocuKing에 Push하면 웹에서도 확인할 수 있습니다.`,
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* 계획 파일 찾기 유틸 (로컬 버전)
|
|
271
|
+
*/
|
|
272
|
+
function findPlanFilesLocal(basePath, planId) {
|
|
273
|
+
const results = [];
|
|
274
|
+
|
|
275
|
+
if (!fs.existsSync(basePath)) {
|
|
276
|
+
return results;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function searchDir(dirPath) {
|
|
280
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
281
|
+
for (const entry of entries) {
|
|
282
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
283
|
+
if (entry.isDirectory()) {
|
|
284
|
+
searchDir(fullPath);
|
|
285
|
+
} else if (entry.isFile() && entry.name.includes(`__${planId}.md`)) {
|
|
286
|
+
results.push(fullPath);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
searchDir(basePath);
|
|
292
|
+
return results;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* docuking_plan 구현 - 작업 계획 생성/업데이트
|
|
297
|
+
*/
|
|
298
|
+
export async function handlePlan(args) {
|
|
299
|
+
const localPath = args.localPath || process.cwd();
|
|
300
|
+
const { planId, title, goal, steps = [], notes } = args;
|
|
301
|
+
|
|
302
|
+
// 협업자 여부에 따라 zz_ai 경로 결정
|
|
303
|
+
// - 오너: localPath/zz_ai_3_Plan/
|
|
304
|
+
// - 협업자: localPath/yy_Coworker_{폴더명}/zz_ai_3_Plan/
|
|
305
|
+
const { basePath } = getAiBasePath(localPath);
|
|
306
|
+
const planBasePath = path.join(basePath, 'zz_ai_3_Plan');
|
|
307
|
+
|
|
308
|
+
// 기존 계획 업데이트 또는 새 계획 생성
|
|
309
|
+
let targetPlanId = planId;
|
|
310
|
+
let isNew = !planId;
|
|
311
|
+
let planFilePath;
|
|
312
|
+
let existingContent = null;
|
|
313
|
+
|
|
314
|
+
if (planId) {
|
|
315
|
+
// 기존 계획 찾기
|
|
316
|
+
const planFiles = findPlanFilesLocal(planBasePath, planId);
|
|
317
|
+
if (planFiles.length === 0) {
|
|
318
|
+
return {
|
|
319
|
+
content: [
|
|
320
|
+
{
|
|
321
|
+
type: 'text',
|
|
322
|
+
text: `오류: planId '${planId}'에 해당하는 계획을 찾을 수 없습니다.`,
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
planFilePath = planFiles[0];
|
|
328
|
+
existingContent = fs.readFileSync(planFilePath, 'utf-8');
|
|
329
|
+
} else {
|
|
330
|
+
// 새 계획 생성
|
|
331
|
+
targetPlanId = generatePlanId();
|
|
332
|
+
const { fileName, timestamp } = generateDateFileName(title, 'P');
|
|
333
|
+
|
|
334
|
+
if (!fs.existsSync(planBasePath)) {
|
|
335
|
+
fs.mkdirSync(planBasePath, { recursive: true });
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// 파일명에 planId 포함
|
|
339
|
+
const fileNameWithId = fileName.replace('.md', `__${targetPlanId}.md`);
|
|
340
|
+
planFilePath = path.join(planBasePath, fileNameWithId);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// 현재 시간
|
|
344
|
+
const now = new Date();
|
|
345
|
+
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')}`;
|
|
346
|
+
|
|
347
|
+
// 단계 상태 문자열 생성
|
|
348
|
+
const stepsMarkdown = steps.length > 0
|
|
349
|
+
? steps.map((step, i) => {
|
|
350
|
+
const statusIcon = step.status === 'done' ? '✅' : step.status === 'in_progress' ? '🔄' : '⬜';
|
|
351
|
+
const resultText = step.result ? ` → ${step.result}` : '';
|
|
352
|
+
return `${i + 1}. ${statusIcon} ${step.name}${resultText}`;
|
|
353
|
+
}).join('\n')
|
|
354
|
+
: '(단계 미정의)';
|
|
355
|
+
|
|
356
|
+
// 진행률 계산
|
|
357
|
+
const doneCount = steps.filter(s => s.status === 'done').length;
|
|
358
|
+
const totalCount = steps.length;
|
|
359
|
+
const progress = totalCount > 0 ? Math.round((doneCount / totalCount) * 100) : 0;
|
|
360
|
+
|
|
361
|
+
// 마크다운 문서 생성
|
|
362
|
+
let document;
|
|
363
|
+
if (isNew) {
|
|
364
|
+
document = `# ${title}
|
|
365
|
+
|
|
366
|
+
> Plan ID: \`${targetPlanId}\`
|
|
367
|
+
> 생성: ${timestamp}
|
|
368
|
+
> 상태: 진행 중 (${progress}%)
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## 목표
|
|
373
|
+
${goal || '(목표 미정의)'}
|
|
374
|
+
|
|
375
|
+
## 진행 단계
|
|
376
|
+
${stepsMarkdown}
|
|
377
|
+
|
|
378
|
+
## 노트
|
|
379
|
+
${notes || '(없음)'}
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## 진행 기록
|
|
384
|
+
- ${timestamp}: 계획 생성
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
*이 문서는 AI 작업 계획에서 자동 생성되었습니다.*
|
|
388
|
+
`;
|
|
389
|
+
} else {
|
|
390
|
+
// 기존 문서 업데이트 (진행 기록에 추가)
|
|
391
|
+
const updateEntry = `- ${timestamp}: 계획 업데이트 (진행률 ${progress}%)`;
|
|
392
|
+
|
|
393
|
+
// 기존 내용에서 섹션 업데이트
|
|
394
|
+
document = existingContent
|
|
395
|
+
.replace(/> 상태: .+/, `> 상태: 진행 중 (${progress}%)`)
|
|
396
|
+
.replace(/## 진행 단계\n[\s\S]*?(?=\n## )/, `## 진행 단계\n${stepsMarkdown}\n\n`)
|
|
397
|
+
.replace(/## 진행 기록\n/, `## 진행 기록\n${updateEntry}\n`);
|
|
398
|
+
|
|
399
|
+
if (notes) {
|
|
400
|
+
document = document.replace(/## 노트\n[\s\S]*?(?=\n---\n\n## 진행 기록)/, `## 노트\n${notes}\n\n`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// 파일 저장
|
|
405
|
+
fs.writeFileSync(planFilePath, document, 'utf-8');
|
|
406
|
+
|
|
407
|
+
const relativePath = path.relative(localPath, planFilePath).replace(/\\/g, '/');
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
content: [
|
|
411
|
+
{
|
|
412
|
+
type: 'text',
|
|
413
|
+
text: `✓ 작업 계획 ${isNew ? '생성' : '업데이트'} 완료!
|
|
414
|
+
|
|
415
|
+
📋 제목: ${title}
|
|
416
|
+
🆔 Plan ID: ${targetPlanId}
|
|
417
|
+
📁 경로: ${relativePath}
|
|
418
|
+
📊 진행률: ${progress}% (${doneCount}/${totalCount})
|
|
419
|
+
|
|
420
|
+
💡 이 planId를 기억해두세요. 나중에 업데이트하거나 완료 처리할 때 필요합니다.
|
|
421
|
+
💡 DocuKing에 Push하면 웹에서도 확인할 수 있습니다.`,
|
|
422
|
+
},
|
|
423
|
+
],
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* docuking_done 구현 - 작업 완료 처리
|
|
429
|
+
*/
|
|
430
|
+
export async function handleDone(args) {
|
|
431
|
+
const localPath = args.localPath || process.cwd();
|
|
432
|
+
const { planId, summary, artifacts = [] } = args;
|
|
433
|
+
|
|
434
|
+
// 협업자 여부에 따라 zz_ai 경로 결정
|
|
435
|
+
// - 오너: localPath/zz_ai_3_Plan/
|
|
436
|
+
// - 협업자: localPath/yy_Coworker_{폴더명}/zz_ai_3_Plan/
|
|
437
|
+
const { basePath } = getAiBasePath(localPath);
|
|
438
|
+
const planBasePath = path.join(basePath, 'zz_ai_3_Plan');
|
|
439
|
+
|
|
440
|
+
// 계획 파일 찾기
|
|
441
|
+
const planFiles = findPlanFilesLocal(planBasePath, planId);
|
|
442
|
+
if (planFiles.length === 0) {
|
|
443
|
+
return {
|
|
444
|
+
content: [
|
|
445
|
+
{
|
|
446
|
+
type: 'text',
|
|
447
|
+
text: `오류: planId '${planId}'에 해당하는 계획을 찾을 수 없습니다.`,
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const planFilePath = planFiles[0];
|
|
454
|
+
let content = fs.readFileSync(planFilePath, 'utf-8');
|
|
455
|
+
|
|
456
|
+
// 현재 시간
|
|
457
|
+
const now = new Date();
|
|
458
|
+
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')}`;
|
|
459
|
+
|
|
460
|
+
// 산출물 목록
|
|
461
|
+
const artifactsMarkdown = artifacts.length > 0
|
|
462
|
+
? artifacts.map(a => `- ${a}`).join('\n')
|
|
463
|
+
: '(없음)';
|
|
464
|
+
|
|
465
|
+
// 완료 섹션 추가
|
|
466
|
+
const completionSection = `
|
|
467
|
+
|
|
468
|
+
## ✅ 완료
|
|
469
|
+
> 완료 시간: ${timestamp}
|
|
470
|
+
|
|
471
|
+
### 요약
|
|
472
|
+
${summary}
|
|
473
|
+
|
|
474
|
+
### 산출물
|
|
475
|
+
${artifactsMarkdown}
|
|
476
|
+
`;
|
|
477
|
+
|
|
478
|
+
// 상태 업데이트 및 완료 섹션 추가
|
|
479
|
+
content = content
|
|
480
|
+
.replace(/> 상태: .+/, `> 상태: ✅ 완료`)
|
|
481
|
+
.replace(/---\n\*이 문서는 AI 작업 계획에서 자동 생성되었습니다.\*/, `${completionSection}\n---\n*이 문서는 AI 작업 계획에서 자동 생성되었습니다.*`);
|
|
482
|
+
|
|
483
|
+
// 진행 기록에 완료 추가
|
|
484
|
+
content = content.replace(/## 진행 기록\n/, `## 진행 기록\n- ${timestamp}: ✅ 작업 완료\n`);
|
|
485
|
+
|
|
486
|
+
// 파일 저장
|
|
487
|
+
fs.writeFileSync(planFilePath, content, 'utf-8');
|
|
488
|
+
|
|
489
|
+
const relativePath = path.relative(localPath, planFilePath).replace(/\\/g, '/');
|
|
490
|
+
|
|
491
|
+
// Plan 완료 시 자동으로 킹푸시 실행 (문서 동기화)
|
|
492
|
+
let pushResult = null;
|
|
493
|
+
let pushMessage = '';
|
|
494
|
+
try {
|
|
495
|
+
pushResult = await handlePush({
|
|
496
|
+
localPath,
|
|
497
|
+
message: `Plan 완료: ${summary.substring(0, 50)}${summary.length > 50 ? '...' : ''}`,
|
|
498
|
+
});
|
|
499
|
+
pushMessage = '\n\n📤 DocuKing 자동 동기화 완료!';
|
|
500
|
+
} catch (e) {
|
|
501
|
+
pushMessage = `\n\n⚠️ DocuKing 동기화 실패: ${e.message}`;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return {
|
|
505
|
+
content: [
|
|
506
|
+
{
|
|
507
|
+
type: 'text',
|
|
508
|
+
text: `✅ 작업 완료 처리됨!
|
|
509
|
+
|
|
510
|
+
🆔 Plan ID: ${planId}
|
|
511
|
+
📁 경로: ${relativePath}
|
|
512
|
+
🕐 완료 시간: ${timestamp}
|
|
513
|
+
|
|
514
|
+
📝 요약: ${summary}
|
|
515
|
+
${artifacts.length > 0 ? `📦 산출물: ${artifacts.length}개` : ''}${pushMessage}`,
|
|
516
|
+
},
|
|
517
|
+
],
|
|
518
|
+
};
|
|
519
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocuKing MCP - 핸들러 통합 모듈
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// 동기화 핸들러
|
|
6
|
+
export {
|
|
7
|
+
handleInit,
|
|
8
|
+
handlePush,
|
|
9
|
+
handlePull,
|
|
10
|
+
handlePullInternal,
|
|
11
|
+
handleList,
|
|
12
|
+
handleStatus,
|
|
13
|
+
} from './sync.js';
|
|
14
|
+
|
|
15
|
+
// 버전 관리 핸들러
|
|
16
|
+
export {
|
|
17
|
+
handleLog,
|
|
18
|
+
handleDiff,
|
|
19
|
+
handleRollback,
|
|
20
|
+
} from './version.js';
|
|
21
|
+
|
|
22
|
+
// 문서 핸들러
|
|
23
|
+
export {
|
|
24
|
+
handleTodo,
|
|
25
|
+
handleTalk,
|
|
26
|
+
handlePlan,
|
|
27
|
+
handleDone,
|
|
28
|
+
} from './docs.js';
|
|
29
|
+
|
|
30
|
+
// 킹캐스트 핸들러
|
|
31
|
+
export {
|
|
32
|
+
executeKingcast,
|
|
33
|
+
handleKingcastStatus,
|
|
34
|
+
} from './kingcast.js';
|