docuking-mcp 2.1.2 → 2.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 +219 -80
- 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;
|
|
@@ -93,72 +116,117 @@ function saveLocalConfig(localPath, config) {
|
|
|
93
116
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
94
117
|
}
|
|
95
118
|
|
|
119
|
+
/**
|
|
120
|
+
* 협업자 여부 및 zz_ai 폴더 기본 경로 반환
|
|
121
|
+
* - 오너: localPath (루트에 zz_ai_* 폴더)
|
|
122
|
+
* - 협업자: localPath/yy_Coworker_{폴더명} (협업자 폴더 안에 zz_ai_* 폴더)
|
|
123
|
+
*/
|
|
124
|
+
function getAiBasePath(localPath) {
|
|
125
|
+
const config = getLocalConfig(localPath);
|
|
126
|
+
if (!config || !config.apiKey) {
|
|
127
|
+
return { isCoworker: false, basePath: localPath, coworkerFolder: null };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const apiKey = config.apiKey;
|
|
131
|
+
const { isCoworker, coworkerFolder } = parseCoworkerFromApiKey(apiKey);
|
|
132
|
+
|
|
133
|
+
if (isCoworker && coworkerFolder) {
|
|
134
|
+
const coworkerFolderPath = path.join(localPath, `yy_Coworker_${coworkerFolder}`);
|
|
135
|
+
return { isCoworker: true, basePath: coworkerFolderPath, coworkerFolder };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return { isCoworker: false, basePath: localPath, coworkerFolder: null };
|
|
139
|
+
}
|
|
140
|
+
|
|
96
141
|
/**
|
|
97
142
|
* CLAUDE.md에 DocuKing MCP 작업 기록 규칙 추가
|
|
98
143
|
* - 파일이 없으면 새로 생성
|
|
99
144
|
* - 파일이 있으면 DocuKing 섹션이 없을 때만 추가
|
|
145
|
+
* - Talk → Todo → Plan → Done 흐름 포함
|
|
100
146
|
*/
|
|
101
147
|
function updateClaudeMd(localPath) {
|
|
102
148
|
const claudeMdPath = path.join(localPath, 'CLAUDE.md');
|
|
103
|
-
const marker = '## DocuKing
|
|
149
|
+
const marker = '## DocuKing 연결됨';
|
|
150
|
+
const oldMarker = '## DocuKing AI 작업 기록 (필수)';
|
|
104
151
|
|
|
105
152
|
const docukingSection = `
|
|
106
153
|
---
|
|
107
154
|
|
|
108
155
|
${marker}
|
|
109
156
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
###
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
###
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
-
|
|
151
|
-
-
|
|
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로 배경 기록
|
|
152
207
|
`;
|
|
153
208
|
|
|
154
209
|
try {
|
|
155
210
|
if (fs.existsSync(claudeMdPath)) {
|
|
156
211
|
// 파일이 있으면 DocuKing 섹션 존재 여부 확인
|
|
157
|
-
|
|
212
|
+
let content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
213
|
+
|
|
214
|
+
// 새 마커가 이미 있으면 스킵
|
|
158
215
|
if (content.includes(marker)) {
|
|
159
|
-
// 이미 DocuKing 섹션이 있으면 스킵
|
|
160
216
|
return;
|
|
161
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
|
+
|
|
162
230
|
// 섹션이 없으면 끝에 추가
|
|
163
231
|
fs.appendFileSync(claudeMdPath, docukingSection, 'utf-8');
|
|
164
232
|
} else {
|
|
@@ -1148,12 +1216,8 @@ docuking_init 호출 시 apiKey 파라미터를 포함해주세요.`,
|
|
|
1148
1216
|
};
|
|
1149
1217
|
}
|
|
1150
1218
|
|
|
1151
|
-
// Co-worker 권한은 API Key 형식에서 판단
|
|
1152
|
-
|
|
1153
|
-
const coworkerMatch = apiKey.match(/^sk_[a-f0-9]+_cw_([^_]*)_/);
|
|
1154
|
-
const isCoworker = !!coworkerMatch;
|
|
1155
|
-
// 코워커 폴더명이 비어있으면 'default'로 기본값 설정
|
|
1156
|
-
const coworkerFolder = coworkerMatch ? (coworkerMatch[1] || 'default') : null;
|
|
1219
|
+
// Co-worker 권한은 API Key 형식에서 판단
|
|
1220
|
+
const { isCoworker, coworkerFolder } = parseCoworkerFromApiKey(apiKey);
|
|
1157
1221
|
|
|
1158
1222
|
// .docuking/config.json에 설정 저장
|
|
1159
1223
|
saveLocalConfig(localPath, {
|
|
@@ -1174,18 +1238,12 @@ docuking_init 호출 시 apiKey 파라미터를 포함해주세요.`,
|
|
|
1174
1238
|
fs.mkdirSync(mainFolderPath, { recursive: true });
|
|
1175
1239
|
}
|
|
1176
1240
|
|
|
1177
|
-
// zz_ai_* 폴더도 함께 생성 (로컬 전용)
|
|
1178
|
-
const aiFolders = ['zz_ai_1_Talk', 'zz_ai_2_Todo', 'zz_ai_3_Plan'];
|
|
1179
|
-
for (const folder of aiFolders) {
|
|
1180
|
-
const folderPath = path.join(localPath, folder);
|
|
1181
|
-
if (!fs.existsSync(folderPath)) {
|
|
1182
|
-
fs.mkdirSync(folderPath, { recursive: true });
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
1241
|
let coworkerFolderName = null;
|
|
1187
1242
|
let coworkerFolderPath = null;
|
|
1188
1243
|
|
|
1244
|
+
// zz_ai_* 폴더 목록
|
|
1245
|
+
const aiFolders = ['zz_ai_1_Talk', 'zz_ai_2_Todo', 'zz_ai_3_Plan'];
|
|
1246
|
+
|
|
1189
1247
|
if (isCoworker) {
|
|
1190
1248
|
// 협업자: yy_Coworker_{폴더명}/ 폴더를 yy_All_Docu/ 밖에 별도 생성
|
|
1191
1249
|
coworkerFolderName = `yy_Coworker_${coworkerFolder}`;
|
|
@@ -1193,6 +1251,35 @@ docuking_init 호출 시 apiKey 파라미터를 포함해주세요.`,
|
|
|
1193
1251
|
if (!fs.existsSync(coworkerFolderPath)) {
|
|
1194
1252
|
fs.mkdirSync(coworkerFolderPath, { recursive: true });
|
|
1195
1253
|
}
|
|
1254
|
+
// 협업자 폴더 안에 _Private/ 생성
|
|
1255
|
+
const coworkerPrivatePath = path.join(coworkerFolderPath, '_Private');
|
|
1256
|
+
if (!fs.existsSync(coworkerPrivatePath)) {
|
|
1257
|
+
fs.mkdirSync(coworkerPrivatePath, { recursive: true });
|
|
1258
|
+
}
|
|
1259
|
+
// 협업자 폴더 안에 zz_ai_* 폴더 생성
|
|
1260
|
+
for (const folder of aiFolders) {
|
|
1261
|
+
const folderPath = path.join(coworkerFolderPath, folder);
|
|
1262
|
+
if (!fs.existsSync(folderPath)) {
|
|
1263
|
+
fs.mkdirSync(folderPath, { recursive: true });
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
} else {
|
|
1267
|
+
// 오너: yy_All_Docu/ 안에 _Infra_Config/와 _Private/ 생성
|
|
1268
|
+
const infraConfigPath = path.join(mainFolderPath, '_Infra_Config');
|
|
1269
|
+
if (!fs.existsSync(infraConfigPath)) {
|
|
1270
|
+
fs.mkdirSync(infraConfigPath, { recursive: true });
|
|
1271
|
+
}
|
|
1272
|
+
const ownerPrivatePath = path.join(mainFolderPath, '_Private');
|
|
1273
|
+
if (!fs.existsSync(ownerPrivatePath)) {
|
|
1274
|
+
fs.mkdirSync(ownerPrivatePath, { recursive: true });
|
|
1275
|
+
}
|
|
1276
|
+
// 오너: 루트에 zz_ai_* 폴더 생성
|
|
1277
|
+
for (const folder of aiFolders) {
|
|
1278
|
+
const folderPath = path.join(localPath, folder);
|
|
1279
|
+
if (!fs.existsSync(folderPath)) {
|
|
1280
|
+
fs.mkdirSync(folderPath, { recursive: true });
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1196
1283
|
}
|
|
1197
1284
|
|
|
1198
1285
|
// 연결 완료 안내 (오너/코워커에 따라 다른 메시지)
|
|
@@ -1297,12 +1384,8 @@ Git처럼 무엇을 변경했는지 명확히 작성해주세요.
|
|
|
1297
1384
|
const projectId = projectInfo.projectId;
|
|
1298
1385
|
const projectName = projectInfo.projectName;
|
|
1299
1386
|
|
|
1300
|
-
// Co-worker 권한은 API Key 형식에서 판단
|
|
1301
|
-
|
|
1302
|
-
const coworkerMatch = apiKey.match(/^sk_[a-f0-9]+_cw_([^_]*)_/);
|
|
1303
|
-
const isCoworker = !!coworkerMatch;
|
|
1304
|
-
// 코워커 폴더명이 비어있으면 'default'로 기본값 설정
|
|
1305
|
-
const coworkerFolder = coworkerMatch ? (coworkerMatch[1] || 'default') : null;
|
|
1387
|
+
// Co-worker 권한은 API Key 형식에서 판단
|
|
1388
|
+
const { isCoworker, coworkerFolder } = parseCoworkerFromApiKey(apiKey);
|
|
1306
1389
|
const coworkerFolderName = isCoworker ? `yy_Coworker_${coworkerFolder}` : null;
|
|
1307
1390
|
|
|
1308
1391
|
// 작업 폴더 결정: 오너는 yy_All_Docu/, 협업자는 yy_Coworker_{폴더명}/ (별도 폴더)
|
|
@@ -1388,6 +1471,54 @@ docuking_init을 먼저 실행하세요.`,
|
|
|
1388
1471
|
console.error(`[DocuKing] frontend/.env.local 백업 실패: ${err.message}`);
|
|
1389
1472
|
}
|
|
1390
1473
|
}
|
|
1474
|
+
|
|
1475
|
+
// package.json 파일 백업 (루트)
|
|
1476
|
+
const packageJsonPath = path.join(localPath, 'package.json');
|
|
1477
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
1478
|
+
if (!fs.existsSync(infraConfigPath)) {
|
|
1479
|
+
fs.mkdirSync(infraConfigPath, { recursive: true });
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
const packageJsonBackupPath = path.join(infraConfigPath, 'package.json');
|
|
1483
|
+
try {
|
|
1484
|
+
fs.copyFileSync(packageJsonPath, packageJsonBackupPath);
|
|
1485
|
+
console.error(`[DocuKing] package.json → _Infra_Config/package.json 자동 백업 완료`);
|
|
1486
|
+
} catch (err) {
|
|
1487
|
+
console.error(`[DocuKing] package.json 백업 실패: ${err.message}`);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
// backend/package.json 파일도 백업
|
|
1492
|
+
const backendPackageJsonPath = path.join(localPath, 'backend', 'package.json');
|
|
1493
|
+
if (fs.existsSync(backendPackageJsonPath)) {
|
|
1494
|
+
if (!fs.existsSync(infraConfigPath)) {
|
|
1495
|
+
fs.mkdirSync(infraConfigPath, { recursive: true });
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
const backendPackageJsonBackupPath = path.join(infraConfigPath, 'backend.package.json');
|
|
1499
|
+
try {
|
|
1500
|
+
fs.copyFileSync(backendPackageJsonPath, backendPackageJsonBackupPath);
|
|
1501
|
+
console.error(`[DocuKing] backend/package.json → _Infra_Config/backend.package.json 자동 백업 완료`);
|
|
1502
|
+
} catch (err) {
|
|
1503
|
+
console.error(`[DocuKing] backend/package.json 백업 실패: ${err.message}`);
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// frontend/package.json 파일도 백업
|
|
1508
|
+
const frontendPackageJsonPath = path.join(localPath, 'frontend', 'package.json');
|
|
1509
|
+
if (fs.existsSync(frontendPackageJsonPath)) {
|
|
1510
|
+
if (!fs.existsSync(infraConfigPath)) {
|
|
1511
|
+
fs.mkdirSync(infraConfigPath, { recursive: true });
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
const frontendPackageJsonBackupPath = path.join(infraConfigPath, 'frontend.package.json');
|
|
1515
|
+
try {
|
|
1516
|
+
fs.copyFileSync(frontendPackageJsonPath, frontendPackageJsonBackupPath);
|
|
1517
|
+
console.error(`[DocuKing] frontend/package.json → _Infra_Config/frontend.package.json 자동 백업 완료`);
|
|
1518
|
+
} catch (err) {
|
|
1519
|
+
console.error(`[DocuKing] frontend/package.json 백업 실패: ${err.message}`);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1391
1522
|
}
|
|
1392
1523
|
|
|
1393
1524
|
// 파일 목록 수집
|
|
@@ -2176,12 +2307,8 @@ async function handleStatus(args) {
|
|
|
2176
2307
|
const projectId = projectInfo.projectId;
|
|
2177
2308
|
const projectName = projectInfo.projectName;
|
|
2178
2309
|
|
|
2179
|
-
// Co-worker 권한은 API Key 형식에서 판단
|
|
2180
|
-
|
|
2181
|
-
const coworkerMatch = apiKey.match(/^sk_[a-f0-9]+_cw_([^_]*)_/);
|
|
2182
|
-
const isCoworker = !!coworkerMatch;
|
|
2183
|
-
// 코워커 폴더명이 비어있으면 'default'로 기본값 설정
|
|
2184
|
-
const coworkerFolder = coworkerMatch ? (coworkerMatch[1] || 'default') : null;
|
|
2310
|
+
// Co-worker 권한은 API Key 형식에서 판단
|
|
2311
|
+
const { isCoworker, coworkerFolder } = parseCoworkerFromApiKey(apiKey);
|
|
2185
2312
|
const coworkerFolderName = isCoworker ? `yy_Coworker_${coworkerFolder}` : null;
|
|
2186
2313
|
|
|
2187
2314
|
// 권한 정보 구성
|
|
@@ -2381,8 +2508,11 @@ function generatePlanId() {
|
|
|
2381
2508
|
async function handleTodo(args) {
|
|
2382
2509
|
const { localPath, action, todo, todoId } = args;
|
|
2383
2510
|
|
|
2384
|
-
//
|
|
2385
|
-
|
|
2511
|
+
// 협업자 여부에 따라 zz_ai 경로 결정
|
|
2512
|
+
// - 오너: localPath/zz_ai_2_Todo/
|
|
2513
|
+
// - 협업자: localPath/yy_Coworker_{폴더명}/zz_ai_2_Todo/
|
|
2514
|
+
const { basePath } = getAiBasePath(localPath);
|
|
2515
|
+
const todoBasePath = path.join(basePath, 'zz_ai_2_Todo');
|
|
2386
2516
|
const todoFilePath = path.join(todoBasePath, 'z_King_Todo.md');
|
|
2387
2517
|
|
|
2388
2518
|
// 폴더 생성
|
|
@@ -2572,8 +2702,11 @@ ${listText}
|
|
|
2572
2702
|
async function handleTalk(args) {
|
|
2573
2703
|
const { localPath, title, content, tags = [] } = args;
|
|
2574
2704
|
|
|
2575
|
-
//
|
|
2576
|
-
|
|
2705
|
+
// 협업자 여부에 따라 zz_ai 경로 결정
|
|
2706
|
+
// - 오너: localPath/zz_ai_1_Talk/
|
|
2707
|
+
// - 협업자: localPath/yy_Coworker_{폴더명}/zz_ai_1_Talk/
|
|
2708
|
+
const { basePath } = getAiBasePath(localPath);
|
|
2709
|
+
const talkBasePath = path.join(basePath, 'zz_ai_1_Talk');
|
|
2577
2710
|
|
|
2578
2711
|
// 날짜 기반 파일명 생성 (T_ 접두사)
|
|
2579
2712
|
const { fileName, timestamp } = generateDateFileName(title, 'T');
|
|
@@ -2625,8 +2758,11 @@ ${content}
|
|
|
2625
2758
|
async function handlePlan(args) {
|
|
2626
2759
|
const { localPath, planId, title, goal, steps = [], notes } = args;
|
|
2627
2760
|
|
|
2628
|
-
//
|
|
2629
|
-
|
|
2761
|
+
// 협업자 여부에 따라 zz_ai 경로 결정
|
|
2762
|
+
// - 오너: localPath/zz_ai_3_Plan/
|
|
2763
|
+
// - 협업자: localPath/yy_Coworker_{폴더명}/zz_ai_3_Plan/
|
|
2764
|
+
const { basePath } = getAiBasePath(localPath);
|
|
2765
|
+
const planBasePath = path.join(basePath, 'zz_ai_3_Plan');
|
|
2630
2766
|
|
|
2631
2767
|
// 기존 계획 업데이트 또는 새 계획 생성
|
|
2632
2768
|
let targetPlanId = planId;
|
|
@@ -2775,8 +2911,11 @@ function findPlanFiles(basePath, planId) {
|
|
|
2775
2911
|
async function handleDone(args) {
|
|
2776
2912
|
const { localPath, planId, summary, artifacts = [] } = args;
|
|
2777
2913
|
|
|
2778
|
-
//
|
|
2779
|
-
|
|
2914
|
+
// 협업자 여부에 따라 zz_ai 경로 결정
|
|
2915
|
+
// - 오너: localPath/zz_ai_3_Plan/
|
|
2916
|
+
// - 협업자: localPath/yy_Coworker_{폴더명}/zz_ai_3_Plan/
|
|
2917
|
+
const { basePath } = getAiBasePath(localPath);
|
|
2918
|
+
const planBasePath = path.join(basePath, 'zz_ai_3_Plan');
|
|
2780
2919
|
|
|
2781
2920
|
// 계획 파일 찾기
|
|
2782
2921
|
const planFiles = findPlanFiles(planBasePath, planId);
|