@silbaram/artifact-driven-agent 0.1.6 → 0.1.9
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/README.md +709 -516
- package/ai-dev-team/.ada-status.json +10 -0
- package/ai-dev-team/.ada-version +6 -0
- package/ai-dev-team/.current-template +1 -0
- package/ai-dev-team/.sessions/logs/20260124-014551-00f04724.log +5 -0
- package/ai-dev-team/.sessions/logs/20260124-014623-cb2b1d44.log +5 -0
- package/ai-dev-team/ada.config.json +15 -0
- package/ai-dev-team/artifacts/api.md +212 -0
- package/ai-dev-team/artifacts/decision.md +72 -0
- package/ai-dev-team/artifacts/improvement-reports/IMP-0000-template.md +57 -0
- package/ai-dev-team/artifacts/plan.md +187 -0
- package/ai-dev-team/artifacts/project.md +193 -0
- package/ai-dev-team/artifacts/sprints/_template/docs/release-notes.md +37 -0
- package/ai-dev-team/artifacts/sprints/_template/meta.md +54 -0
- package/ai-dev-team/artifacts/sprints/_template/retrospective.md +50 -0
- package/ai-dev-team/artifacts/sprints/_template/review-reports/review-template.md +49 -0
- package/ai-dev-team/artifacts/sprints/_template/tasks/task-template.md +43 -0
- package/ai-dev-team/artifacts/ui.md +104 -0
- package/ai-dev-team/roles/analyzer.md +265 -0
- package/ai-dev-team/roles/developer.md +222 -0
- package/ai-dev-team/roles/documenter.md +715 -0
- package/ai-dev-team/roles/improver.md +461 -0
- package/ai-dev-team/roles/manager.md +544 -0
- package/ai-dev-team/roles/planner.md +398 -0
- package/ai-dev-team/roles/reviewer.md +294 -0
- package/ai-dev-team/rules/api-change.md +198 -0
- package/ai-dev-team/rules/document-priority.md +199 -0
- package/ai-dev-team/rules/escalation.md +172 -0
- package/ai-dev-team/rules/iteration.md +236 -0
- package/ai-dev-team/rules/rfc.md +31 -0
- package/ai-dev-team/rules/rollback.md +218 -0
- package/bin/cli.js +49 -5
- package/core/artifacts/sprints/_template/meta.md +4 -4
- package/core/docs-templates/mkdocs/docs/architecture/overview.md +29 -0
- package/core/docs-templates/mkdocs/docs/changelog.md +36 -0
- package/core/docs-templates/mkdocs/docs/contributing/contributing.md +60 -0
- package/core/docs-templates/mkdocs/docs/getting-started/configuration.md +51 -0
- package/core/docs-templates/mkdocs/docs/getting-started/installation.md +41 -0
- package/core/docs-templates/mkdocs/docs/getting-started/quick-start.md +56 -0
- package/core/docs-templates/mkdocs/docs/guides/api-reference.md +83 -0
- package/core/docs-templates/mkdocs/docs/index.md +32 -0
- package/core/docs-templates/mkdocs/mkdocs.yml +86 -0
- package/core/roles/analyzer.md +32 -10
- package/core/roles/developer.md +222 -223
- package/core/roles/documenter.md +592 -170
- package/core/roles/improver.md +461 -0
- package/core/roles/manager.md +4 -1
- package/core/roles/planner.md +160 -10
- package/core/roles/reviewer.md +31 -3
- package/core/rules/document-priority.md +2 -1
- package/core/rules/rollback.md +3 -3
- package/package.json +1 -1
- package/src/commands/config.js +371 -0
- package/src/commands/docs.js +502 -0
- package/src/commands/interactive.js +324 -33
- package/src/commands/monitor.js +236 -0
- package/src/commands/run.js +360 -122
- package/src/commands/sessions.js +270 -70
- package/src/commands/setup.js +22 -1
- package/src/commands/sprint.js +295 -54
- package/src/commands/status.js +34 -1
- package/src/commands/upgrade.js +416 -0
- package/src/commands/validate.js +4 -3
- package/src/index.js +1 -0
- package/src/ui/dashboard.js +518 -0
- package/src/ui/keyHandler.js +147 -0
- package/src/ui/quickActions.js +111 -0
- package/src/utils/config.js +74 -0
- package/src/utils/files.js +70 -3
- package/src/utils/sessionState.js +472 -328
- package/src/utils/sessionState.process.test.js +101 -0
- package/src/utils/sessionState.test.js +183 -0
- package/src/utils/sprintUtils.js +134 -0
- package/src/utils/taskParser.js +134 -0
- package/src/utils/taskParser.test.js +76 -0
- package/ai-dev-team/artifacts/features/_template/qa.md +0 -16
- package/examples/todo-app/README.md +0 -23
- package/examples/todo-app/artifacts/backlog.md +0 -23
- package/examples/todo-app/artifacts/plan.md +0 -23
- package/examples/todo-app/artifacts/project.md +0 -23
package/core/rules/rollback.md
CHANGED
|
@@ -73,13 +73,13 @@ Developer: 수정된 설계로 재구현
|
|
|
73
73
|
```
|
|
74
74
|
Reviewer REJECT
|
|
75
75
|
↓
|
|
76
|
-
Task 상태:
|
|
76
|
+
Task 상태: IN_REVIEW → IN_DEV
|
|
77
77
|
↓
|
|
78
78
|
review-report.md에 FAIL 사유 기록
|
|
79
79
|
↓
|
|
80
80
|
원인에 따라 담당자 결정
|
|
81
81
|
↓
|
|
82
|
-
수정 후 재리뷰
|
|
82
|
+
수정 후 재리뷰
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
---
|
|
@@ -143,7 +143,7 @@ Task 상태 복원 → 재진행
|
|
|
143
143
|
↓
|
|
144
144
|
Task 상태에 따른 처리:
|
|
145
145
|
├── DONE → 재검증 필요 여부 판단
|
|
146
|
-
├──
|
|
146
|
+
├── IN_REVIEW → 재검증
|
|
147
147
|
├── IN_DEV → 수정 반영
|
|
148
148
|
└── IN_SPRINT/READY → 문서만 갱신
|
|
149
149
|
```
|
package/package.json
CHANGED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { readConfig, writeConfig, getConfigPath } from '../utils/config.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* [CLI] 설정 관리 명령어
|
|
7
|
+
*/
|
|
8
|
+
export async function config(action, key, value) {
|
|
9
|
+
const configPath = getConfigPath();
|
|
10
|
+
const currentConfig = readConfig();
|
|
11
|
+
|
|
12
|
+
// 1. 인자 없이 실행 → 대화형 모드
|
|
13
|
+
if (!action) {
|
|
14
|
+
await runInteractiveSet(currentConfig);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 2. 설정 조회 (List/Show)
|
|
19
|
+
if (action === 'list' || action === 'show') {
|
|
20
|
+
console.log(chalk.cyan('━'.repeat(60)));
|
|
21
|
+
console.log(chalk.cyan.bold('⚙️ ADA Configuration'));
|
|
22
|
+
console.log(chalk.gray(` Path: ${configPath}`));
|
|
23
|
+
console.log(chalk.cyan('━'.repeat(60)));
|
|
24
|
+
console.log('');
|
|
25
|
+
console.log(JSON.stringify(currentConfig, null, 2));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 2. 설정 값 확인 (Get)
|
|
30
|
+
if (action === 'get') {
|
|
31
|
+
if (!key) {
|
|
32
|
+
// 키 입력이 없으면 목록에서 선택하게 할 수도 있음 (여기선 생략)
|
|
33
|
+
console.error(chalk.red('❌ 조회할 설정 키를 입력하세요. (예: roles.manager)'));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
const val = getValue(currentConfig, key);
|
|
37
|
+
console.log(val);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 3. 설정 변경 (Set)
|
|
42
|
+
if (action === 'set') {
|
|
43
|
+
// 인자가 부족하면 대화형 모드로 진입
|
|
44
|
+
if (!key || !value) {
|
|
45
|
+
await runInteractiveSet(currentConfig);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 인자가 있으면 바로 변경
|
|
50
|
+
updateConfig(currentConfig, key, value);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.error(chalk.red(`❌ 알 수 없는 명령입니다: ${action}`));
|
|
55
|
+
console.log(chalk.gray('사용 가능: list, get, set'));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 대화형 설정 변경 (개선된 버전)
|
|
60
|
+
*/
|
|
61
|
+
async function runInteractiveSet(currentConfig) {
|
|
62
|
+
console.log(chalk.cyan('\n🛠️ 설정 변경 마법사'));
|
|
63
|
+
console.log(chalk.gray(' 역할별 AI 도구를 설정합니다.\n'));
|
|
64
|
+
|
|
65
|
+
const tools = ['claude', 'gemini', 'gpt', 'codex', 'copilot'];
|
|
66
|
+
const pendingChanges = {}; // 변경 예정 사항 추적
|
|
67
|
+
|
|
68
|
+
// 메인 루프
|
|
69
|
+
while (true) {
|
|
70
|
+
// 현재 설정 상태 표시
|
|
71
|
+
printCurrentSettings(currentConfig, pendingChanges);
|
|
72
|
+
|
|
73
|
+
// 메인 메뉴
|
|
74
|
+
const { action } = await inquirer.prompt([{
|
|
75
|
+
type: 'list',
|
|
76
|
+
name: 'action',
|
|
77
|
+
message: '무엇을 하시겠습니까?',
|
|
78
|
+
choices: [
|
|
79
|
+
{ name: '📝 역할별 도구 설정', value: 'set_role' },
|
|
80
|
+
{ name: '🔧 기본 도구 변경', value: 'set_default' },
|
|
81
|
+
{ name: '📦 프리셋 적용', value: 'preset' },
|
|
82
|
+
new inquirer.Separator(),
|
|
83
|
+
{ name: '💾 저장하고 종료', value: 'save' },
|
|
84
|
+
{ name: '❌ 변경 취소', value: 'cancel' }
|
|
85
|
+
]
|
|
86
|
+
}]);
|
|
87
|
+
|
|
88
|
+
if (action === 'set_role') {
|
|
89
|
+
await setRoleTool(currentConfig, pendingChanges, tools);
|
|
90
|
+
} else if (action === 'set_default') {
|
|
91
|
+
await setDefaultTool(currentConfig, pendingChanges, tools);
|
|
92
|
+
} else if (action === 'preset') {
|
|
93
|
+
await applyPreset(currentConfig, pendingChanges);
|
|
94
|
+
} else if (action === 'save') {
|
|
95
|
+
if (Object.keys(pendingChanges).length === 0) {
|
|
96
|
+
console.log(chalk.yellow('\n변경 사항이 없습니다.'));
|
|
97
|
+
} else {
|
|
98
|
+
// 변경 사항 적용
|
|
99
|
+
for (const [key, value] of Object.entries(pendingChanges)) {
|
|
100
|
+
setValue(currentConfig, key, value);
|
|
101
|
+
}
|
|
102
|
+
writeConfig(currentConfig);
|
|
103
|
+
console.log(chalk.green('\n✅ 설정이 저장되었습니다.'));
|
|
104
|
+
printChangeSummary(pendingChanges);
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
} else if (action === 'cancel') {
|
|
108
|
+
if (Object.keys(pendingChanges).length > 0) {
|
|
109
|
+
const { confirmCancel } = await inquirer.prompt([{
|
|
110
|
+
type: 'confirm',
|
|
111
|
+
name: 'confirmCancel',
|
|
112
|
+
message: '변경 사항이 있습니다. 정말 취소하시겠습니까?',
|
|
113
|
+
default: false
|
|
114
|
+
}]);
|
|
115
|
+
if (!confirmCancel) continue;
|
|
116
|
+
}
|
|
117
|
+
console.log(chalk.gray('\n취소되었습니다.'));
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 현재 설정 상태 출력
|
|
125
|
+
*/
|
|
126
|
+
function printCurrentSettings(config, pendingChanges) {
|
|
127
|
+
console.log(chalk.cyan('\n┌─────────────────────────────────────────┐'));
|
|
128
|
+
console.log(chalk.cyan('│') + chalk.bold(' 현재 설정 상태 ') + chalk.cyan('│'));
|
|
129
|
+
console.log(chalk.cyan('├─────────────────────────────────────────┤'));
|
|
130
|
+
|
|
131
|
+
// 기본 도구
|
|
132
|
+
const defaultTool = pendingChanges['defaults.tool'] || config.defaults?.tool || 'claude';
|
|
133
|
+
const defaultChanged = pendingChanges['defaults.tool'] ? chalk.yellow(' (변경됨)') : '';
|
|
134
|
+
console.log(chalk.cyan('│') + ` 기본 도구: ${chalk.bold(defaultTool)}${defaultChanged}`.padEnd(48) + chalk.cyan('│'));
|
|
135
|
+
|
|
136
|
+
console.log(chalk.cyan('├─────────────────────────────────────────┤'));
|
|
137
|
+
|
|
138
|
+
// 역할별 설정
|
|
139
|
+
const roles = Object.keys(config.roles || {});
|
|
140
|
+
roles.forEach(role => {
|
|
141
|
+
const currentValue = pendingChanges[`roles.${role}`] || config.roles[role];
|
|
142
|
+
const changed = pendingChanges[`roles.${role}`] ? chalk.yellow(' *') : '';
|
|
143
|
+
const line = ` ${role.padEnd(12)}: ${currentValue}${changed}`;
|
|
144
|
+
console.log(chalk.cyan('│') + line.padEnd(48) + chalk.cyan('│'));
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
console.log(chalk.cyan('└─────────────────────────────────────────┘'));
|
|
148
|
+
|
|
149
|
+
if (Object.keys(pendingChanges).length > 0) {
|
|
150
|
+
console.log(chalk.yellow(` (* 저장되지 않은 변경 ${Object.keys(pendingChanges).length}개)`));
|
|
151
|
+
}
|
|
152
|
+
console.log('');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 역할별 도구 설정
|
|
157
|
+
*/
|
|
158
|
+
async function setRoleTool(config, pendingChanges, tools) {
|
|
159
|
+
const roles = Object.keys(config.roles || {});
|
|
160
|
+
|
|
161
|
+
const { selectedRole } = await inquirer.prompt([{
|
|
162
|
+
type: 'list',
|
|
163
|
+
name: 'selectedRole',
|
|
164
|
+
message: '설정할 역할을 선택하세요:',
|
|
165
|
+
choices: [
|
|
166
|
+
...roles.map(role => {
|
|
167
|
+
const current = pendingChanges[`roles.${role}`] || config.roles[role];
|
|
168
|
+
return { name: `${role} (현재: ${current})`, value: role };
|
|
169
|
+
}),
|
|
170
|
+
new inquirer.Separator(),
|
|
171
|
+
{ name: '↩️ 뒤로가기', value: '__back__' }
|
|
172
|
+
]
|
|
173
|
+
}]);
|
|
174
|
+
|
|
175
|
+
if (selectedRole === '__back__') return;
|
|
176
|
+
|
|
177
|
+
const currentTool = pendingChanges[`roles.${selectedRole}`] || config.roles[selectedRole];
|
|
178
|
+
|
|
179
|
+
const { selectedTool } = await inquirer.prompt([{
|
|
180
|
+
type: 'list',
|
|
181
|
+
name: 'selectedTool',
|
|
182
|
+
message: `${selectedRole}에 사용할 도구를 선택하세요:`,
|
|
183
|
+
choices: [
|
|
184
|
+
...tools.map(tool => ({
|
|
185
|
+
name: tool === currentTool ? `${tool} (현재)` : tool,
|
|
186
|
+
value: tool
|
|
187
|
+
})),
|
|
188
|
+
new inquirer.Separator(),
|
|
189
|
+
{ name: '↩️ 뒤로가기', value: '__back__' }
|
|
190
|
+
],
|
|
191
|
+
default: currentTool
|
|
192
|
+
}]);
|
|
193
|
+
|
|
194
|
+
if (selectedTool === '__back__') return;
|
|
195
|
+
|
|
196
|
+
if (selectedTool !== config.roles[selectedRole]) {
|
|
197
|
+
pendingChanges[`roles.${selectedRole}`] = selectedTool;
|
|
198
|
+
console.log(chalk.green(`\n ✓ ${selectedRole}: ${config.roles[selectedRole]} → ${selectedTool}`));
|
|
199
|
+
} else {
|
|
200
|
+
// 원래 값으로 돌아간 경우 pending에서 제거
|
|
201
|
+
delete pendingChanges[`roles.${selectedRole}`];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 기본 도구 설정
|
|
207
|
+
*/
|
|
208
|
+
async function setDefaultTool(config, pendingChanges, tools) {
|
|
209
|
+
const currentDefault = pendingChanges['defaults.tool'] || config.defaults?.tool || 'claude';
|
|
210
|
+
|
|
211
|
+
const { selectedTool } = await inquirer.prompt([{
|
|
212
|
+
type: 'list',
|
|
213
|
+
name: 'selectedTool',
|
|
214
|
+
message: '기본 도구를 선택하세요:',
|
|
215
|
+
choices: [
|
|
216
|
+
...tools.map(tool => ({
|
|
217
|
+
name: tool === currentDefault ? `${tool} (현재)` : tool,
|
|
218
|
+
value: tool
|
|
219
|
+
})),
|
|
220
|
+
new inquirer.Separator(),
|
|
221
|
+
{ name: '↩️ 뒤로가기', value: '__back__' }
|
|
222
|
+
],
|
|
223
|
+
default: currentDefault
|
|
224
|
+
}]);
|
|
225
|
+
|
|
226
|
+
if (selectedTool === '__back__') return;
|
|
227
|
+
|
|
228
|
+
const originalDefault = config.defaults?.tool || 'claude';
|
|
229
|
+
if (selectedTool !== originalDefault) {
|
|
230
|
+
pendingChanges['defaults.tool'] = selectedTool;
|
|
231
|
+
console.log(chalk.green(`\n ✓ 기본 도구: ${originalDefault} → ${selectedTool}`));
|
|
232
|
+
} else {
|
|
233
|
+
delete pendingChanges['defaults.tool'];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* 프리셋 적용
|
|
239
|
+
*/
|
|
240
|
+
async function applyPreset(config, pendingChanges) {
|
|
241
|
+
const presets = {
|
|
242
|
+
'all_claude': {
|
|
243
|
+
name: '🔵 All Claude',
|
|
244
|
+
description: '모든 역할에 Claude 사용',
|
|
245
|
+
settings: { default: 'claude', roles: {} }
|
|
246
|
+
},
|
|
247
|
+
'all_gemini': {
|
|
248
|
+
name: '🟢 All Gemini',
|
|
249
|
+
description: '모든 역할에 Gemini 사용',
|
|
250
|
+
settings: { default: 'gemini', roles: {} }
|
|
251
|
+
},
|
|
252
|
+
'mixed_optimal': {
|
|
253
|
+
name: '🎨 Mixed Optimal',
|
|
254
|
+
description: 'Planner/Reviewer: Claude, Developer: Gemini',
|
|
255
|
+
settings: {
|
|
256
|
+
default: 'claude',
|
|
257
|
+
roles: {
|
|
258
|
+
planner: 'claude',
|
|
259
|
+
developer: 'gemini',
|
|
260
|
+
reviewer: 'claude',
|
|
261
|
+
documenter: 'claude',
|
|
262
|
+
manager: 'claude',
|
|
263
|
+
improver: 'claude',
|
|
264
|
+
analyzer: 'claude'
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
'dev_gemini': {
|
|
269
|
+
name: '⚡ Dev Gemini + Review Claude',
|
|
270
|
+
description: '개발은 Gemini, 리뷰는 Claude',
|
|
271
|
+
settings: {
|
|
272
|
+
default: 'claude',
|
|
273
|
+
roles: {
|
|
274
|
+
developer: 'gemini'
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const { selectedPreset } = await inquirer.prompt([{
|
|
281
|
+
type: 'list',
|
|
282
|
+
name: 'selectedPreset',
|
|
283
|
+
message: '적용할 프리셋을 선택하세요:',
|
|
284
|
+
choices: [
|
|
285
|
+
...Object.entries(presets).map(([key, preset]) => ({
|
|
286
|
+
name: `${preset.name} - ${chalk.gray(preset.description)}`,
|
|
287
|
+
value: key
|
|
288
|
+
})),
|
|
289
|
+
new inquirer.Separator(),
|
|
290
|
+
{ name: '↩️ 뒤로가기', value: '__back__' }
|
|
291
|
+
]
|
|
292
|
+
}]);
|
|
293
|
+
|
|
294
|
+
if (selectedPreset === '__back__') return;
|
|
295
|
+
|
|
296
|
+
const preset = presets[selectedPreset];
|
|
297
|
+
const roles = Object.keys(config.roles || {});
|
|
298
|
+
|
|
299
|
+
// 기본 도구 설정
|
|
300
|
+
if (preset.settings.default) {
|
|
301
|
+
const originalDefault = config.defaults?.tool || 'claude';
|
|
302
|
+
if (preset.settings.default !== originalDefault) {
|
|
303
|
+
pendingChanges['defaults.tool'] = preset.settings.default;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// 역할별 설정
|
|
308
|
+
roles.forEach(role => {
|
|
309
|
+
let newValue;
|
|
310
|
+
if (preset.settings.roles && preset.settings.roles[role]) {
|
|
311
|
+
newValue = preset.settings.roles[role];
|
|
312
|
+
} else {
|
|
313
|
+
newValue = preset.settings.default || 'claude';
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (newValue !== config.roles[role]) {
|
|
317
|
+
pendingChanges[`roles.${role}`] = newValue;
|
|
318
|
+
} else {
|
|
319
|
+
delete pendingChanges[`roles.${role}`];
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
console.log(chalk.green(`\n ✓ 프리셋 '${preset.name}' 적용됨 (저장 필요)`));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* 변경 사항 요약 출력
|
|
328
|
+
*/
|
|
329
|
+
function printChangeSummary(changes) {
|
|
330
|
+
console.log(chalk.cyan('\n변경 내역:'));
|
|
331
|
+
for (const [key, value] of Object.entries(changes)) {
|
|
332
|
+
console.log(chalk.gray(` - ${key}: ${value}`));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* 설정 업데이트 및 저장 공통 로직
|
|
338
|
+
*/
|
|
339
|
+
function updateConfig(config, key, value) {
|
|
340
|
+
const validTools = ['claude', 'gemini', 'gpt', 'codex', 'copilot'];
|
|
341
|
+
|
|
342
|
+
// 유효성 검사
|
|
343
|
+
if (key.startsWith('roles.') || key === 'defaults.tool') {
|
|
344
|
+
if (!validTools.includes(value)) {
|
|
345
|
+
console.warn(chalk.yellow(`⚠️ 경고: '${value}'는 알려진 도구 목록(${validTools.join(', ')})에 없습니다.`));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
setValue(config, key, value);
|
|
350
|
+
writeConfig(config);
|
|
351
|
+
|
|
352
|
+
console.log(chalk.green(`\n✅ 설정이 변경되었습니다: ${key} = ${value}`));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ----------------------------------------------------------------------
|
|
356
|
+
// 헬퍼 함수: 점(.)으로 구분된 키로 객체 접근 (Lodash get/set 대용)
|
|
357
|
+
// ----------------------------------------------------------------------
|
|
358
|
+
|
|
359
|
+
function getValue(obj, path) {
|
|
360
|
+
return path.split('.').reduce((acc, part) => acc && acc[part], obj);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function setValue(obj, path, value) {
|
|
364
|
+
const parts = path.split('.');
|
|
365
|
+
const last = parts.pop();
|
|
366
|
+
const target = parts.reduce((acc, part) => {
|
|
367
|
+
if (!acc[part]) acc[part] = {};
|
|
368
|
+
return acc[part];
|
|
369
|
+
}, obj);
|
|
370
|
+
target[last] = value;
|
|
371
|
+
}
|