docuking-mcp 2.1.3 → 2.2.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/index.js +117 -70
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -40,6 +40,29 @@ import crypto from 'crypto';
|
|
|
40
40
|
// 환경변수에서 API 엔드포인트 설정 (키는 로컬 config에서 읽음)
|
|
41
41
|
const API_ENDPOINT = process.env.DOCUKING_API_ENDPOINT || 'https://docuking.ai/api';
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* API 키에서 협업자 폴더명 추출
|
|
45
|
+
* API 키 형식: sk_프로젝트8자_cw_폴더명_랜덤48자(hex)
|
|
46
|
+
* hex는 0-9, a-f만 포함하므로 언더스코어 없음
|
|
47
|
+
* 따라서 _cw_ 이후 마지막 _ 전까지가 폴더명
|
|
48
|
+
*/
|
|
49
|
+
function parseCoworkerFromApiKey(apiKey) {
|
|
50
|
+
if (!apiKey) return { isCoworker: false, coworkerFolder: null };
|
|
51
|
+
|
|
52
|
+
const cwIndex = apiKey.indexOf('_cw_');
|
|
53
|
+
if (cwIndex === -1) return { isCoworker: false, coworkerFolder: null };
|
|
54
|
+
|
|
55
|
+
const afterCw = apiKey.substring(cwIndex + 4);
|
|
56
|
+
const lastUnderscore = afterCw.lastIndexOf('_');
|
|
57
|
+
if (lastUnderscore === -1) return { isCoworker: false, coworkerFolder: null };
|
|
58
|
+
|
|
59
|
+
const folderName = afterCw.substring(0, lastUnderscore);
|
|
60
|
+
return {
|
|
61
|
+
isCoworker: !!folderName,
|
|
62
|
+
coworkerFolder: folderName || null,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
43
66
|
// 파일 크기 제한 (150MB) - Base64 인코딩 시 약 200MB, 서버 제한 200MB와 일치
|
|
44
67
|
const MAX_FILE_SIZE_MB = 150;
|
|
45
68
|
const MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024;
|
|
@@ -105,13 +128,11 @@ function getAiBasePath(localPath) {
|
|
|
105
128
|
}
|
|
106
129
|
|
|
107
130
|
const apiKey = config.apiKey;
|
|
108
|
-
|
|
109
|
-
const coworkerMatch = apiKey.match(/_cw_([^_]+)_$/);
|
|
131
|
+
const { isCoworker, coworkerFolder } = parseCoworkerFromApiKey(apiKey);
|
|
110
132
|
|
|
111
|
-
if (
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
return { isCoworker: true, basePath: coworkerFolderPath, coworkerFolder: folderName };
|
|
133
|
+
if (isCoworker && coworkerFolder) {
|
|
134
|
+
const coworkerFolderPath = path.join(localPath, `yy_Coworker_${coworkerFolder}`);
|
|
135
|
+
return { isCoworker: true, basePath: coworkerFolderPath, coworkerFolder };
|
|
115
136
|
}
|
|
116
137
|
|
|
117
138
|
return { isCoworker: false, basePath: localPath, coworkerFolder: null };
|
|
@@ -121,68 +142,91 @@ function getAiBasePath(localPath) {
|
|
|
121
142
|
* CLAUDE.md에 DocuKing MCP 작업 기록 규칙 추가
|
|
122
143
|
* - 파일이 없으면 새로 생성
|
|
123
144
|
* - 파일이 있으면 DocuKing 섹션이 없을 때만 추가
|
|
145
|
+
* - Talk → Todo → Plan → Done 흐름 포함
|
|
124
146
|
*/
|
|
125
147
|
function updateClaudeMd(localPath) {
|
|
126
148
|
const claudeMdPath = path.join(localPath, 'CLAUDE.md');
|
|
127
|
-
const marker = '## DocuKing
|
|
149
|
+
const marker = '## DocuKing 연결됨';
|
|
150
|
+
const oldMarker = '## DocuKing AI 작업 기록 (필수)';
|
|
128
151
|
|
|
129
152
|
const docukingSection = `
|
|
130
153
|
---
|
|
131
154
|
|
|
132
155
|
${marker}
|
|
133
156
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
###
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
###
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
157
|
+
### MCP 도구
|
|
158
|
+
| 도구 | 용도 |
|
|
159
|
+
|------|------|
|
|
160
|
+
| docuking_push | 문서 업로드 ("올려줘") |
|
|
161
|
+
| docuking_pull | 문서 다운로드 ("가져와") |
|
|
162
|
+
| docuking_list | 서버 파일 목록 조회 |
|
|
163
|
+
| docuking_status | 동기화 상태 확인 |
|
|
164
|
+
| docuking_log | 커밋 히스토리 조회 |
|
|
165
|
+
| docuking_diff | 버전 간 차이 비교 |
|
|
166
|
+
| docuking_rollback | 특정 커밋으로 되돌림 |
|
|
167
|
+
| docuking_plan | 작업 시작 시 계획 기록 |
|
|
168
|
+
| docuking_done | 작업 완료 시 결과 기록 |
|
|
169
|
+
| docuking_todo | 킹투두 (프로젝트 할일) |
|
|
170
|
+
| docuking_talk | 대화록 저장 |
|
|
171
|
+
|
|
172
|
+
### 작업 흐름 (필수)
|
|
173
|
+
|
|
174
|
+
**1단계: Talk 기록 (작업 원인/배경)**
|
|
175
|
+
- 사용자 요청의 배경, 논의 내용을 먼저 기록
|
|
176
|
+
- Plan을 세우기 전에 반드시 Talk로 "왜 이 작업을 하는지" 남김
|
|
177
|
+
|
|
178
|
+
**2단계: Todo 등록 (할일 등록)**
|
|
179
|
+
- 의미 있는 작업이면 킹투두에 등록
|
|
180
|
+
- 나중에 추적/공유할 가치가 있는 작업
|
|
181
|
+
|
|
182
|
+
**3단계: Plan 생성 → 작업 → Done 완료**
|
|
183
|
+
- 기존대로 plan → 작업 → done
|
|
184
|
+
|
|
185
|
+
### Talk 자동 기록 (docuking_talk)
|
|
186
|
+
|
|
187
|
+
**다음 상황에서 AI가 자동으로 기록:**
|
|
188
|
+
- 설계 방향 결정 (A vs B 선택, "왜 이렇게?")
|
|
189
|
+
- 버그 원인 분석 및 해결책 논의
|
|
190
|
+
- 아키텍처/구조 변경 논의
|
|
191
|
+
- 사용자가 중요한 결정을 내릴 때
|
|
192
|
+
- Plan을 세우게 된 원인/배경 논의
|
|
193
|
+
|
|
194
|
+
### Todo 자동 등록 (docuking_todo)
|
|
195
|
+
|
|
196
|
+
**다음 상황에서 AI가 자동으로 등록:**
|
|
197
|
+
- 사용자가 "나중에 해야 할 것" 언급 시
|
|
198
|
+
- 작업 중 발견한 추가 개선사항
|
|
199
|
+
- "TODO", "FIXME" 성격의 발견 사항
|
|
200
|
+
- 의미 있는 작업 시작 시 (추적용)
|
|
201
|
+
|
|
202
|
+
### 규칙
|
|
203
|
+
1. yy_All_Docu/ 폴더만 동기화 대상
|
|
204
|
+
2. Push는 사용자 요청 시에만
|
|
205
|
+
3. **Talk → Todo → Plan → Done** 순서 준수
|
|
206
|
+
4. Plan 전에 반드시 Talk로 배경 기록
|
|
176
207
|
`;
|
|
177
208
|
|
|
178
209
|
try {
|
|
179
210
|
if (fs.existsSync(claudeMdPath)) {
|
|
180
211
|
// 파일이 있으면 DocuKing 섹션 존재 여부 확인
|
|
181
|
-
|
|
212
|
+
let content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
213
|
+
|
|
214
|
+
// 새 마커가 이미 있으면 스킵
|
|
182
215
|
if (content.includes(marker)) {
|
|
183
|
-
// 이미 DocuKing 섹션이 있으면 스킵
|
|
184
216
|
return;
|
|
185
217
|
}
|
|
218
|
+
|
|
219
|
+
// 구버전 마커가 있으면 교체
|
|
220
|
+
if (content.includes(oldMarker)) {
|
|
221
|
+
// 구버전 섹션 찾아서 교체 (--- 부터 끝까지 또는 다음 --- 전까지)
|
|
222
|
+
const oldSectionStart = content.lastIndexOf('---\n\n' + oldMarker);
|
|
223
|
+
if (oldSectionStart !== -1) {
|
|
224
|
+
content = content.substring(0, oldSectionStart) + docukingSection;
|
|
225
|
+
fs.writeFileSync(claudeMdPath, content, 'utf-8');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
186
230
|
// 섹션이 없으면 끝에 추가
|
|
187
231
|
fs.appendFileSync(claudeMdPath, docukingSection, 'utf-8');
|
|
188
232
|
} else {
|
|
@@ -1172,12 +1216,8 @@ docuking_init 호출 시 apiKey 파라미터를 포함해주세요.`,
|
|
|
1172
1216
|
};
|
|
1173
1217
|
}
|
|
1174
1218
|
|
|
1175
|
-
// Co-worker 권한은 API Key 형식에서 판단
|
|
1176
|
-
|
|
1177
|
-
const coworkerMatch = apiKey.match(/^sk_[a-f0-9]+_cw_([^_]*)_/);
|
|
1178
|
-
const isCoworker = !!coworkerMatch;
|
|
1179
|
-
// 코워커 폴더명이 비어있으면 'default'로 기본값 설정
|
|
1180
|
-
const coworkerFolder = coworkerMatch ? (coworkerMatch[1] || 'default') : null;
|
|
1219
|
+
// Co-worker 권한은 API Key 형식에서 판단
|
|
1220
|
+
const { isCoworker, coworkerFolder } = parseCoworkerFromApiKey(apiKey);
|
|
1181
1221
|
|
|
1182
1222
|
// .docuking/config.json에 설정 저장
|
|
1183
1223
|
saveLocalConfig(localPath, {
|
|
@@ -1344,12 +1384,8 @@ Git처럼 무엇을 변경했는지 명확히 작성해주세요.
|
|
|
1344
1384
|
const projectId = projectInfo.projectId;
|
|
1345
1385
|
const projectName = projectInfo.projectName;
|
|
1346
1386
|
|
|
1347
|
-
// Co-worker 권한은 API Key 형식에서 판단
|
|
1348
|
-
|
|
1349
|
-
const coworkerMatch = apiKey.match(/^sk_[a-f0-9]+_cw_([^_]*)_/);
|
|
1350
|
-
const isCoworker = !!coworkerMatch;
|
|
1351
|
-
// 코워커 폴더명이 비어있으면 'default'로 기본값 설정
|
|
1352
|
-
const coworkerFolder = coworkerMatch ? (coworkerMatch[1] || 'default') : null;
|
|
1387
|
+
// Co-worker 권한은 API Key 형식에서 판단
|
|
1388
|
+
const { isCoworker, coworkerFolder } = parseCoworkerFromApiKey(apiKey);
|
|
1353
1389
|
const coworkerFolderName = isCoworker ? `yy_Coworker_${coworkerFolder}` : null;
|
|
1354
1390
|
|
|
1355
1391
|
// 작업 폴더 결정: 오너는 yy_All_Docu/, 협업자는 yy_Coworker_{폴더명}/ (별도 폴더)
|
|
@@ -2006,11 +2042,26 @@ async function handlePull(args) {
|
|
|
2006
2042
|
}
|
|
2007
2043
|
}
|
|
2008
2044
|
|
|
2045
|
+
// 결과 요약
|
|
2046
|
+
const successCount = results.filter(r => r.startsWith('✓')).length;
|
|
2047
|
+
const failCount = results.filter(r => r.startsWith('✗')).length;
|
|
2048
|
+
|
|
2049
|
+
// 실패한 파일만 상세 표시, 성공은 개수만
|
|
2050
|
+
const failedFiles = results.filter(r => r.startsWith('✗'));
|
|
2051
|
+
|
|
2052
|
+
let summary = `Pull 완료! (${successCount}개 성공`;
|
|
2053
|
+
if (failCount > 0) {
|
|
2054
|
+
summary += `, ${failCount}개 실패)`;
|
|
2055
|
+
summary += `\n\n실패 목록:\n${failedFiles.join('\n')}`;
|
|
2056
|
+
} else {
|
|
2057
|
+
summary += ')';
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2009
2060
|
return {
|
|
2010
2061
|
content: [
|
|
2011
2062
|
{
|
|
2012
2063
|
type: 'text',
|
|
2013
|
-
text:
|
|
2064
|
+
text: summary,
|
|
2014
2065
|
},
|
|
2015
2066
|
],
|
|
2016
2067
|
};
|
|
@@ -2271,12 +2322,8 @@ async function handleStatus(args) {
|
|
|
2271
2322
|
const projectId = projectInfo.projectId;
|
|
2272
2323
|
const projectName = projectInfo.projectName;
|
|
2273
2324
|
|
|
2274
|
-
// Co-worker 권한은 API Key 형식에서 판단
|
|
2275
|
-
|
|
2276
|
-
const coworkerMatch = apiKey.match(/^sk_[a-f0-9]+_cw_([^_]*)_/);
|
|
2277
|
-
const isCoworker = !!coworkerMatch;
|
|
2278
|
-
// 코워커 폴더명이 비어있으면 'default'로 기본값 설정
|
|
2279
|
-
const coworkerFolder = coworkerMatch ? (coworkerMatch[1] || 'default') : null;
|
|
2325
|
+
// Co-worker 권한은 API Key 형식에서 판단
|
|
2326
|
+
const { isCoworker, coworkerFolder } = parseCoworkerFromApiKey(apiKey);
|
|
2280
2327
|
const coworkerFolderName = isCoworker ? `yy_Coworker_${coworkerFolder}` : null;
|
|
2281
2328
|
|
|
2282
2329
|
// 권한 정보 구성
|