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/lib/config.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocuKing MCP - 설정 관리 모듈
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
|
|
8
|
+
// 환경변수에서 API 엔드포인트 설정
|
|
9
|
+
export const API_ENDPOINT = process.env.DOCUKING_API_ENDPOINT || 'https://docuking.ai/api';
|
|
10
|
+
|
|
11
|
+
// 파일 크기 제한 (150MB)
|
|
12
|
+
export const MAX_FILE_SIZE_MB = 150;
|
|
13
|
+
export const MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* API 키에서 협업자 폴더명 추출
|
|
17
|
+
* API 키 형식: sk_프로젝트8자_cw_폴더명_랜덤48자(hex)
|
|
18
|
+
*/
|
|
19
|
+
export function parseCoworkerFromApiKey(apiKey) {
|
|
20
|
+
if (!apiKey) return { isCoworker: false, coworkerFolder: null };
|
|
21
|
+
|
|
22
|
+
const cwIndex = apiKey.indexOf('_cw_');
|
|
23
|
+
if (cwIndex === -1) return { isCoworker: false, coworkerFolder: null };
|
|
24
|
+
|
|
25
|
+
const afterCw = apiKey.substring(cwIndex + 4);
|
|
26
|
+
const lastUnderscore = afterCw.lastIndexOf('_');
|
|
27
|
+
if (lastUnderscore === -1) return { isCoworker: false, coworkerFolder: null };
|
|
28
|
+
|
|
29
|
+
const folderName = afterCw.substring(0, lastUnderscore);
|
|
30
|
+
return {
|
|
31
|
+
isCoworker: !!folderName,
|
|
32
|
+
coworkerFolder: folderName || null,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 로컬 프로젝트의 .docuking/config.json에서 설정 읽기
|
|
38
|
+
*/
|
|
39
|
+
export function getLocalConfig(localPath) {
|
|
40
|
+
const configPath = path.join(localPath, '.docuking', 'config.json');
|
|
41
|
+
|
|
42
|
+
if (!fs.existsSync(configPath)) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
48
|
+
return JSON.parse(content);
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.error('[DocuKing] config.json 파싱 실패:', e.message);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 로컬 config에서 API 키 가져오기
|
|
57
|
+
*/
|
|
58
|
+
export function getApiKey(localPath) {
|
|
59
|
+
const config = getLocalConfig(localPath);
|
|
60
|
+
return config?.apiKey || '';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 로컬 config 저장하기 (.docuking/config.json)
|
|
65
|
+
*/
|
|
66
|
+
export function saveLocalConfig(localPath, config) {
|
|
67
|
+
const docukingDir = path.join(localPath, '.docuking');
|
|
68
|
+
|
|
69
|
+
if (!fs.existsSync(docukingDir)) {
|
|
70
|
+
fs.mkdirSync(docukingDir, { recursive: true });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const configPath = path.join(docukingDir, 'config.json');
|
|
74
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 협업자 여부 및 zz_ai 폴더 기본 경로 반환
|
|
79
|
+
*/
|
|
80
|
+
export function getAiBasePath(localPath) {
|
|
81
|
+
const config = getLocalConfig(localPath);
|
|
82
|
+
if (!config || !config.apiKey) {
|
|
83
|
+
return { isCoworker: false, basePath: localPath, coworkerFolder: null };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const apiKey = config.apiKey;
|
|
87
|
+
const { isCoworker, coworkerFolder } = parseCoworkerFromApiKey(apiKey);
|
|
88
|
+
|
|
89
|
+
if (isCoworker && coworkerFolder) {
|
|
90
|
+
const coworkerFolderPath = path.join(localPath, `yy_Coworker_${coworkerFolder}`);
|
|
91
|
+
return { isCoworker: true, basePath: coworkerFolderPath, coworkerFolder };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { isCoworker: false, basePath: localPath, coworkerFolder: null };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 프로젝트 정보 조회 (로컬 config에서)
|
|
99
|
+
*/
|
|
100
|
+
export function getProjectInfo(localPath) {
|
|
101
|
+
const config = getLocalConfig(localPath);
|
|
102
|
+
|
|
103
|
+
if (!config || !config.projectId) {
|
|
104
|
+
return {
|
|
105
|
+
error: `오류: 이 프로젝트는 DocuKing에 연결되지 않았습니다.
|
|
106
|
+
먼저 docuking_init을 실행하세요.`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
projectId: config.projectId,
|
|
112
|
+
projectName: config.projectName,
|
|
113
|
+
};
|
|
114
|
+
}
|
package/lib/files.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocuKing MCP - 파일 처리 모듈
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { MAX_FILE_SIZE_BYTES, MAX_FILE_SIZE_MB } from './config.js';
|
|
8
|
+
|
|
9
|
+
// 파일 타입 정의
|
|
10
|
+
export const FILE_TYPES = {
|
|
11
|
+
// 텍스트 파일 (UTF-8)
|
|
12
|
+
TEXT: [
|
|
13
|
+
'.md', '.txt', '.json', '.xml', '.yaml', '.yml',
|
|
14
|
+
'.html', '.htm', '.css', '.js', '.ts', '.jsx', '.tsx',
|
|
15
|
+
'.py', '.java', '.go', '.rs', '.rb', '.php', '.c', '.cpp', '.h',
|
|
16
|
+
'.csv', '.svg', '.sql', '.sh', '.ps1', '.bat', '.cmd',
|
|
17
|
+
'.env', '.gitignore', '.dockerignore', '.editorconfig',
|
|
18
|
+
'.properties', '.ini', '.toml', '.conf', '.cfg',
|
|
19
|
+
],
|
|
20
|
+
|
|
21
|
+
// 바이너리 파일 (Base64)
|
|
22
|
+
BINARY: [
|
|
23
|
+
// 이미지
|
|
24
|
+
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.ico', '.tiff',
|
|
25
|
+
// 오피스
|
|
26
|
+
'.xlsx', '.xls', '.docx', '.doc', '.pptx', '.ppt',
|
|
27
|
+
// PDF
|
|
28
|
+
'.pdf',
|
|
29
|
+
// 한글
|
|
30
|
+
'.hwpx', '.hwp',
|
|
31
|
+
],
|
|
32
|
+
|
|
33
|
+
// 제외 파일 (업로드 거부)
|
|
34
|
+
EXCLUDED: [
|
|
35
|
+
// 압축/아카이브
|
|
36
|
+
'.zip', '.tar', '.gz', '.tgz', '.7z', '.rar', '.tar.Z',
|
|
37
|
+
// Java 아카이브
|
|
38
|
+
'.jar', '.war', '.ear', '.class',
|
|
39
|
+
// 실행파일
|
|
40
|
+
'.exe', '.msi', '.dll', '.so', '.dylib', '.com', '.app', '.pkg', '.deb', '.rpm',
|
|
41
|
+
// macOS 스크립트
|
|
42
|
+
'.scpt',
|
|
43
|
+
// 디스크 이미지
|
|
44
|
+
'.iso', '.dmg', '.img', '.vhd', '.vmdk',
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 파일 확장자로 파일 타입 결정
|
|
50
|
+
*/
|
|
51
|
+
export function getFileType(fileName) {
|
|
52
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
53
|
+
|
|
54
|
+
// .tar.gz, .tar.Z 등 복합 확장자 처리
|
|
55
|
+
if (fileName.endsWith('.tar.gz') || fileName.endsWith('.tar.Z')) {
|
|
56
|
+
return 'binary';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (FILE_TYPES.EXCLUDED.includes(ext)) {
|
|
60
|
+
return 'excluded';
|
|
61
|
+
}
|
|
62
|
+
if (FILE_TYPES.BINARY.includes(ext)) {
|
|
63
|
+
return 'binary';
|
|
64
|
+
}
|
|
65
|
+
if (FILE_TYPES.TEXT.includes(ext)) {
|
|
66
|
+
return 'text';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 알 수 없는 확장자는 바이너리로 처리 (안전)
|
|
70
|
+
return 'binary';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* yy_All_Docu 폴더 존재 확인
|
|
75
|
+
*/
|
|
76
|
+
export function hasAllDocuFolder(projectPath) {
|
|
77
|
+
const allDocuPath = path.join(projectPath, 'yy_All_Docu');
|
|
78
|
+
return fs.existsSync(allDocuPath);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 빈 폴더 수집 (재귀)
|
|
83
|
+
*/
|
|
84
|
+
export function collectEmptyFolders(basePath, relativePath, results) {
|
|
85
|
+
const fullPath = path.join(basePath, relativePath);
|
|
86
|
+
if (!fs.existsSync(fullPath)) return;
|
|
87
|
+
|
|
88
|
+
const entries = fs.readdirSync(fullPath, { withFileTypes: true });
|
|
89
|
+
|
|
90
|
+
const hasFiles = entries.some(e => e.isFile());
|
|
91
|
+
const subDirs = entries.filter(e => e.isDirectory());
|
|
92
|
+
|
|
93
|
+
if (!hasFiles && subDirs.length === 0) {
|
|
94
|
+
results.push(relativePath);
|
|
95
|
+
} else {
|
|
96
|
+
for (const entry of subDirs) {
|
|
97
|
+
const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
98
|
+
collectEmptyFolders(basePath, entryRelPath, results);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 디렉토리 재귀 탐색 (Push용)
|
|
105
|
+
*/
|
|
106
|
+
export function collectFilesSimple(basePath, relativePath, results, excludedFiles = null, largeFiles = null) {
|
|
107
|
+
const fullPath = path.join(basePath, relativePath);
|
|
108
|
+
if (!fs.existsSync(fullPath)) return;
|
|
109
|
+
|
|
110
|
+
const entries = fs.readdirSync(fullPath, { withFileTypes: true });
|
|
111
|
+
|
|
112
|
+
for (const entry of entries) {
|
|
113
|
+
const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
114
|
+
|
|
115
|
+
if (entry.isDirectory()) {
|
|
116
|
+
collectFilesSimple(basePath, entryRelPath, results, excludedFiles, largeFiles);
|
|
117
|
+
} else if (entry.isFile()) {
|
|
118
|
+
const fileType = getFileType(entry.name);
|
|
119
|
+
|
|
120
|
+
if (fileType === 'excluded') {
|
|
121
|
+
console.error(`[DocuKing] 제외됨: ${entryRelPath} (지원하지 않는 파일 형식)`);
|
|
122
|
+
if (excludedFiles) {
|
|
123
|
+
excludedFiles.push(entryRelPath);
|
|
124
|
+
}
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const fileFullPath = path.join(fullPath, entry.name);
|
|
129
|
+
const stats = fs.statSync(fileFullPath);
|
|
130
|
+
const fileSizeMB = stats.size / (1024 * 1024);
|
|
131
|
+
|
|
132
|
+
if (stats.size > MAX_FILE_SIZE_BYTES) {
|
|
133
|
+
console.error(`[DocuKing] ⚠️ 대용량 파일 제외: ${entryRelPath} (${fileSizeMB.toFixed(1)}MB > ${MAX_FILE_SIZE_MB}MB)`);
|
|
134
|
+
if (largeFiles) {
|
|
135
|
+
largeFiles.push({ path: entryRelPath, sizeMB: fileSizeMB.toFixed(1) });
|
|
136
|
+
}
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
results.push({
|
|
141
|
+
path: entryRelPath,
|
|
142
|
+
fullPath: fileFullPath,
|
|
143
|
+
fileType,
|
|
144
|
+
sizeMB: fileSizeMB,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 디렉토리 재귀 탐색 (기존 호환용)
|
|
152
|
+
*/
|
|
153
|
+
export function collectFiles(basePath, relativePath, results, excludedFiles = null, largeFiles = null) {
|
|
154
|
+
const fullPath = path.join(basePath, relativePath);
|
|
155
|
+
const entries = fs.readdirSync(fullPath, { withFileTypes: true });
|
|
156
|
+
|
|
157
|
+
for (const entry of entries) {
|
|
158
|
+
const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
159
|
+
|
|
160
|
+
if (entry.isDirectory()) {
|
|
161
|
+
collectFiles(basePath, entryRelPath, results, excludedFiles, largeFiles);
|
|
162
|
+
} else if (entry.isFile()) {
|
|
163
|
+
const fileType = getFileType(entry.name);
|
|
164
|
+
|
|
165
|
+
if (fileType === 'excluded') {
|
|
166
|
+
console.error(`[DocuKing] 제외됨: ${entryRelPath} (지원하지 않는 파일 형식)`);
|
|
167
|
+
if (excludedFiles) {
|
|
168
|
+
excludedFiles.push(entryRelPath);
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const fileFullPath = path.join(fullPath, entry.name);
|
|
174
|
+
const stats = fs.statSync(fileFullPath);
|
|
175
|
+
const fileSizeMB = stats.size / (1024 * 1024);
|
|
176
|
+
|
|
177
|
+
if (stats.size > MAX_FILE_SIZE_BYTES) {
|
|
178
|
+
console.error(`[DocuKing] ⚠️ 대용량 파일 제외: ${entryRelPath} (${fileSizeMB.toFixed(1)}MB > ${MAX_FILE_SIZE_MB}MB)`);
|
|
179
|
+
if (largeFiles) {
|
|
180
|
+
largeFiles.push({ path: entryRelPath, sizeMB: fileSizeMB.toFixed(1) });
|
|
181
|
+
}
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
results.push({
|
|
186
|
+
path: entryRelPath,
|
|
187
|
+
fullPath: fileFullPath,
|
|
188
|
+
fileType,
|
|
189
|
+
sizeMB: fileSizeMB,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 트리 구조를 평탄화
|
|
197
|
+
*/
|
|
198
|
+
export function flattenTree(tree, prefix = '') {
|
|
199
|
+
const results = [];
|
|
200
|
+
|
|
201
|
+
for (const item of tree) {
|
|
202
|
+
const itemPath = prefix ? `${prefix}/${item.name}` : item.name;
|
|
203
|
+
|
|
204
|
+
if (item.type === 'file') {
|
|
205
|
+
results.push({ path: itemPath, name: item.name });
|
|
206
|
+
} else if (item.children) {
|
|
207
|
+
results.push(...flattenTree(item.children, itemPath));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return results;
|
|
212
|
+
}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocuKing MCP - 라이브러리 모듈 인덱스
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// 설정 관련
|
|
6
|
+
export {
|
|
7
|
+
API_ENDPOINT,
|
|
8
|
+
MAX_FILE_SIZE_MB,
|
|
9
|
+
MAX_FILE_SIZE_BYTES,
|
|
10
|
+
parseCoworkerFromApiKey,
|
|
11
|
+
getLocalConfig,
|
|
12
|
+
getApiKey,
|
|
13
|
+
saveLocalConfig,
|
|
14
|
+
getAiBasePath,
|
|
15
|
+
getProjectInfo,
|
|
16
|
+
} from './config.js';
|
|
17
|
+
|
|
18
|
+
// 파일 처리
|
|
19
|
+
export {
|
|
20
|
+
FILE_TYPES,
|
|
21
|
+
getFileType,
|
|
22
|
+
hasAllDocuFolder,
|
|
23
|
+
collectEmptyFolders,
|
|
24
|
+
collectFilesSimple,
|
|
25
|
+
collectFiles,
|
|
26
|
+
flattenTree,
|
|
27
|
+
} from './files.js';
|
|
28
|
+
|
|
29
|
+
// 초기화 헬퍼
|
|
30
|
+
export {
|
|
31
|
+
updateClaudeMd,
|
|
32
|
+
setupAutoApproval,
|
|
33
|
+
updateGitignore,
|
|
34
|
+
} from './init.js';
|
|
35
|
+
|
|
36
|
+
// 유틸리티
|
|
37
|
+
export {
|
|
38
|
+
generateDateFileName,
|
|
39
|
+
generatePlanId,
|
|
40
|
+
findPlanFiles,
|
|
41
|
+
} from './utils.js';
|
package/lib/init.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocuKing MCP - 초기화 헬퍼 모듈
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* CLAUDE.md에 DocuKing 섹션 추가
|
|
10
|
+
*/
|
|
11
|
+
export function updateClaudeMd(localPath) {
|
|
12
|
+
const claudeMdPath = path.join(localPath, 'CLAUDE.md');
|
|
13
|
+
const marker = '## DocuKing 연결됨';
|
|
14
|
+
const oldMarker = '## DocuKing AI 작업 기록 (필수)';
|
|
15
|
+
|
|
16
|
+
const docukingSection = `
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
${marker}
|
|
20
|
+
|
|
21
|
+
### MCP 도구
|
|
22
|
+
| 도구 | 용도 |
|
|
23
|
+
|------|------|
|
|
24
|
+
| docuking_push | 문서 업로드 ("올려줘") |
|
|
25
|
+
| docuking_pull | 문서 다운로드 ("가져와") |
|
|
26
|
+
| docuking_list | 서버 파일 목록 조회 |
|
|
27
|
+
| docuking_status | 동기화 상태 확인 |
|
|
28
|
+
| docuking_log | 커밋 히스토리 조회 |
|
|
29
|
+
| docuking_diff | 버전 간 차이 비교 |
|
|
30
|
+
| docuking_rollback | 특정 커밋으로 되돌림 |
|
|
31
|
+
| docuking_plan | 작업 시작 시 계획 기록 |
|
|
32
|
+
| docuking_done | 작업 완료 시 결과 기록 |
|
|
33
|
+
| docuking_todo | 킹투두 (프로젝트 할일) |
|
|
34
|
+
| docuking_talk | 대화록 저장 |
|
|
35
|
+
|
|
36
|
+
### 작업 흐름 (필수)
|
|
37
|
+
|
|
38
|
+
**1단계: Talk 기록 (작업 원인/배경)**
|
|
39
|
+
- 사용자 요청의 배경, 논의 내용을 먼저 기록
|
|
40
|
+
- Plan을 세우기 전에 반드시 Talk로 "왜 이 작업을 하는지" 남김
|
|
41
|
+
|
|
42
|
+
**2단계: Todo 등록 (할일 등록)**
|
|
43
|
+
- 의미 있는 작업이면 킹투두에 등록
|
|
44
|
+
- 나중에 추적/공유할 가치가 있는 작업
|
|
45
|
+
|
|
46
|
+
**3단계: Plan 생성 → 작업 → Done 완료**
|
|
47
|
+
- 기존대로 plan → 작업 → done
|
|
48
|
+
|
|
49
|
+
### Talk 자동 기록 (docuking_talk)
|
|
50
|
+
|
|
51
|
+
**다음 상황에서 AI가 자동으로 기록:**
|
|
52
|
+
- 설계 방향 결정 (A vs B 선택, "왜 이렇게?")
|
|
53
|
+
- 버그 원인 분석 및 해결책 논의
|
|
54
|
+
- 아키텍처/구조 변경 논의
|
|
55
|
+
- 사용자가 중요한 결정을 내릴 때
|
|
56
|
+
- Plan을 세우게 된 원인/배경 논의
|
|
57
|
+
|
|
58
|
+
### Todo 자동 등록 (docuking_todo)
|
|
59
|
+
|
|
60
|
+
**다음 상황에서 AI가 자동으로 등록:**
|
|
61
|
+
- 사용자가 "나중에 해야 할 것" 언급 시
|
|
62
|
+
- 작업 중 발견한 추가 개선사항
|
|
63
|
+
- "TODO", "FIXME" 성격의 발견 사항
|
|
64
|
+
- 의미 있는 작업 시작 시 (추적용)
|
|
65
|
+
|
|
66
|
+
### 규칙
|
|
67
|
+
1. 동기화 대상: yy_All_Docu/ + zz_ai_*/ 폴더 모두
|
|
68
|
+
2. 킹푸시는 Plan 완료(done) 시 자동 실행
|
|
69
|
+
3. **Talk → Todo → Plan → Done** 순서 준수
|
|
70
|
+
4. Plan 전에 반드시 Talk로 배경 기록
|
|
71
|
+
|
|
72
|
+
### Git과 킹푸시는 별개
|
|
73
|
+
- **Git 커밋/푸시**: 단위 작업 완료 시 "커밋/푸시할까요?" 제안 (소스 보호)
|
|
74
|
+
- **킹푸시**: Plan 완료(docuking_done) 시 자동 실행 (문서 동기화)
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## DocuKing 협업 정책 (AI 필독)
|
|
79
|
+
|
|
80
|
+
### 핵심 원칙: 남의 제사상을 건드리지 않는다
|
|
81
|
+
|
|
82
|
+
각자 자기 영역만 수정 가능, 남의 영역은 읽기만.
|
|
83
|
+
|
|
84
|
+
### Push/Pull 정책
|
|
85
|
+
|
|
86
|
+
| 역할 | Push (올리기) | Pull (내리기) | 삭제 |
|
|
87
|
+
|------|---------------|---------------|------|
|
|
88
|
+
| **오너** | \`yy_All_Docu/\` + \`zz_ai_*/\` | 합집합 전체 | 자기 영역만 |
|
|
89
|
+
| **협업자** | \`yy_Coworker_{본인}/\` (안에 zz_ai_* 포함) | 합집합 전체 | 자기 영역만 |
|
|
90
|
+
|
|
91
|
+
### 합집합이란?
|
|
92
|
+
- 서버 = 오너 파일 + 모든 협업자 파일
|
|
93
|
+
- Pull하면 전체가 로컬로 내려옴
|
|
94
|
+
- 경로만 알면 누구 파일이든 읽기 가능
|
|
95
|
+
|
|
96
|
+
### 협업자 참여 흐름
|
|
97
|
+
1. **초대 수락** (웹) → 프로젝트 진입, 자기 빈폴더 생성됨
|
|
98
|
+
2. **MCP init** → 로컬에 자기 폴더 생성
|
|
99
|
+
3. **Pull** → 합집합 전체 내려옴
|
|
100
|
+
4. **작업** → \`yy_Coworker_{본인}/\` 안에서
|
|
101
|
+
5. **Push** → 자기 폴더만 올라감
|
|
102
|
+
|
|
103
|
+
### 폴더 구조 (Pull 후)
|
|
104
|
+
\`\`\`
|
|
105
|
+
project/
|
|
106
|
+
├── yy_All_Docu/ ← 오너 문서 (읽기만)
|
|
107
|
+
├── yy_Coworker_a_Kim/ ← 협업자 Kim
|
|
108
|
+
│ ├── 작업문서.md
|
|
109
|
+
│ └── zz_ai_1_Talk/ ← Kim의 AI 기록
|
|
110
|
+
├── yy_Coworker_b_Lee/ ← 협업자 Lee
|
|
111
|
+
└── zz_ai_*/ ← 오너의 AI 폴더
|
|
112
|
+
\`\`\`
|
|
113
|
+
|
|
114
|
+
### AI가 협업자에게 안내할 것
|
|
115
|
+
- 작업 폴더: \`yy_Coworker_{폴더명}/\`
|
|
116
|
+
- 남의 파일: 읽기만 가능, 수정 불가
|
|
117
|
+
- 수정 제안: 자기 폴더에 작성 또는 직접 연락
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 킹캐스트 (KingCast) - 정책 자동 배포
|
|
122
|
+
|
|
123
|
+
오너가 \`yy_All_Docu/_Policy/\` 폴더에 정책을 작성하고 Push하면,
|
|
124
|
+
협업자가 Pull할 때 자동으로 \`.claude/rules/local/\`에 로컬화됩니다.
|
|
125
|
+
|
|
126
|
+
### 킹캐스트 대상 폴더
|
|
127
|
+
- \`yy_All_Docu/_Infra_Config/\` - 환경 설정 (참조용, 로컬화하지 않음)
|
|
128
|
+
- \`yy_All_Docu/_Policy/\` - AI 행동 지침 (로컬화 대상)
|
|
129
|
+
|
|
130
|
+
### 킹캐스트 동작
|
|
131
|
+
1. Pull 실행
|
|
132
|
+
2. _Infra_Config/, _Policy/ 폴더 변경 감지
|
|
133
|
+
3. \`_Policy/\` 파일들을 \`.claude/rules/local/\`에 서브파일 구조로 복사
|
|
134
|
+
4. 협업자의 경우 변수 치환 (폴더명, 브랜치명 등 로컬화)
|
|
135
|
+
5. \`_Infra_Config/\`는 참조용으로 내려오지만, 협업자가 직접 다룰 정보가 아니므로 로컬화하지 않음
|
|
136
|
+
|
|
137
|
+
### Git 제외 정책
|
|
138
|
+
|
|
139
|
+
**Git에서 제외 (민감 정보):**
|
|
140
|
+
- \`.docuking/config.json\` - API 키 포함
|
|
141
|
+
- \`.claude/rules/local/_coworker_config.md\` - 협업자별 로컬 설정
|
|
142
|
+
|
|
143
|
+
**Git에 포함 (백업 대상):**
|
|
144
|
+
- \`.claude/rules/local/*.md\` - 정책 파일 (폴더구조, API규칙 등 민감정보 없음)
|
|
145
|
+
|
|
146
|
+
### AI 필독
|
|
147
|
+
- Pull 후 "📢 킹캐스트" 메시지가 표시되면, 정책 파일의 내용을 읽어 변경사항을 확인하세요.
|
|
148
|
+
- \`.claude/rules/local/00_index.md\`를 읽고 정책 문서 목록을 확인하세요.
|
|
149
|
+
- 각 정책 파일의 내용을 읽고 규칙을 따라 작업하세요.
|
|
150
|
+
`;
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
if (fs.existsSync(claudeMdPath)) {
|
|
154
|
+
let content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
155
|
+
|
|
156
|
+
if (content.includes(marker)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (content.includes(oldMarker)) {
|
|
161
|
+
const oldSectionStart = content.lastIndexOf('---\n\n' + oldMarker);
|
|
162
|
+
if (oldSectionStart !== -1) {
|
|
163
|
+
content = content.substring(0, oldSectionStart) + docukingSection;
|
|
164
|
+
fs.writeFileSync(claudeMdPath, content, 'utf-8');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
fs.appendFileSync(claudeMdPath, docukingSection, 'utf-8');
|
|
170
|
+
} else {
|
|
171
|
+
const newContent = `# Project Instructions
|
|
172
|
+
|
|
173
|
+
> AI가 이 프로젝트에서 작업할 때 참고할 지침
|
|
174
|
+
${docukingSection}`;
|
|
175
|
+
fs.writeFileSync(claudeMdPath, newContent, 'utf-8');
|
|
176
|
+
}
|
|
177
|
+
} catch (e) {
|
|
178
|
+
console.error('[DocuKing] CLAUDE.md 업데이트 실패:', e.message);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* IDE별 자동 승인 설정 추가
|
|
184
|
+
*/
|
|
185
|
+
export function setupAutoApproval(localPath) {
|
|
186
|
+
const claudeSettingsPath = path.join(localPath, '.claude', 'settings.local.json');
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
let settings = {};
|
|
190
|
+
|
|
191
|
+
if (fs.existsSync(claudeSettingsPath)) {
|
|
192
|
+
const content = fs.readFileSync(claudeSettingsPath, 'utf-8');
|
|
193
|
+
settings = JSON.parse(content);
|
|
194
|
+
} else {
|
|
195
|
+
const claudeDir = path.join(localPath, '.claude');
|
|
196
|
+
if (!fs.existsSync(claudeDir)) {
|
|
197
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!settings.permissions) settings.permissions = {};
|
|
202
|
+
if (!settings.permissions.allow) settings.permissions.allow = [];
|
|
203
|
+
|
|
204
|
+
let changed = false;
|
|
205
|
+
|
|
206
|
+
if (!settings.enableAllProjectMcpServers) {
|
|
207
|
+
settings.enableAllProjectMcpServers = true;
|
|
208
|
+
changed = true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const wildcardPermission = 'mcp__docuking__*';
|
|
212
|
+
if (!settings.permissions.allow.includes(wildcardPermission)) {
|
|
213
|
+
settings.permissions.allow.push(wildcardPermission);
|
|
214
|
+
changed = true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (changed) {
|
|
218
|
+
fs.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
219
|
+
}
|
|
220
|
+
} catch (e) {
|
|
221
|
+
console.error('[DocuKing] Claude Code 설정 업데이트 실패:', e.message);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* .gitignore에 DocuKing 폴더 추가
|
|
227
|
+
*
|
|
228
|
+
* Git 제외 정책:
|
|
229
|
+
* - 문서 폴더 (yy_*, zz_*): DocuKing으로 관리하므로 Git 제외
|
|
230
|
+
* - .docuking/config.json: API 키 포함 (민감)
|
|
231
|
+
* - _coworker_config.md: 협업자별 로컬 설정 (각자 생성)
|
|
232
|
+
*
|
|
233
|
+
* Git 포함 (백업 대상):
|
|
234
|
+
* - .claude/rules/local/*.md: 정책 파일들 (폴더구조, API규칙 등 - 민감정보 없음)
|
|
235
|
+
*/
|
|
236
|
+
export function updateGitignore(localPath) {
|
|
237
|
+
const gitignorePath = path.join(localPath, '.gitignore');
|
|
238
|
+
const marker = '# DocuKing (문서는 DocuKing으로 관리)';
|
|
239
|
+
const docukingEntries = `
|
|
240
|
+
${marker}
|
|
241
|
+
yy_All_Docu/
|
|
242
|
+
yy_Coworker_*/
|
|
243
|
+
zz_ai_1_Talk/
|
|
244
|
+
zz_ai_2_Todo/
|
|
245
|
+
zz_ai_3_Plan/
|
|
246
|
+
.docuking/config.json
|
|
247
|
+
.claude/rules/local/_coworker_config.md
|
|
248
|
+
`;
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
if (fs.existsSync(gitignorePath)) {
|
|
252
|
+
let content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
253
|
+
|
|
254
|
+
// 이전 버전에서 .claude/rules/local/ 전체 제외했던 경우, 수정
|
|
255
|
+
if (content.includes('.claude/rules/local/') && !content.includes('.claude/rules/local/_coworker_config.md')) {
|
|
256
|
+
content = content.replace('.claude/rules/local/', '.claude/rules/local/_coworker_config.md');
|
|
257
|
+
fs.writeFileSync(gitignorePath, content, 'utf-8');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (content.includes(marker)) {
|
|
261
|
+
return; // 이미 마커가 있으면 완료
|
|
262
|
+
}
|
|
263
|
+
fs.appendFileSync(gitignorePath, docukingEntries, 'utf-8');
|
|
264
|
+
} else {
|
|
265
|
+
fs.writeFileSync(gitignorePath, docukingEntries.trim() + '\n', 'utf-8');
|
|
266
|
+
}
|
|
267
|
+
} catch (e) {
|
|
268
|
+
console.error('[DocuKing] .gitignore 업데이트 실패:', e.message);
|
|
269
|
+
}
|
|
270
|
+
}
|