@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
|
@@ -2,21 +2,33 @@ import chalk from 'chalk';
|
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
3
|
import { setup } from './setup.js';
|
|
4
4
|
import { run } from './run.js';
|
|
5
|
+
import { status } from './status.js';
|
|
6
|
+
import { sessions } from './sessions.js';
|
|
7
|
+
import { logs } from './logs.js';
|
|
8
|
+
import { config } from './config.js';
|
|
9
|
+
import { monitor } from './monitor.js';
|
|
10
|
+
import { upgrade } from './upgrade.js';
|
|
11
|
+
import { validate } from './validate.js';
|
|
12
|
+
import sprintCommand from './sprint.js';
|
|
13
|
+
import docsCommand from './docs.js';
|
|
5
14
|
import {
|
|
6
15
|
isWorkspaceSetup,
|
|
7
16
|
getAvailableRoles,
|
|
8
|
-
getAvailableTools
|
|
9
|
-
getAvailableTemplates
|
|
17
|
+
getAvailableTools
|
|
10
18
|
} from '../utils/files.js';
|
|
19
|
+
import { getToolForRole } from '../utils/config.js';
|
|
11
20
|
|
|
21
|
+
/**
|
|
22
|
+
* 대화형 메인 메뉴 (ada 명령어 인자 없이 실행 시)
|
|
23
|
+
*/
|
|
12
24
|
export async function interactive() {
|
|
13
25
|
console.log('');
|
|
14
|
-
console.log(chalk.cyan('━'.repeat(
|
|
15
|
-
console.log(chalk.cyan.bold('🤖 Artifact-Driven AI Agent'));
|
|
16
|
-
console.log(chalk.cyan('━'.repeat(
|
|
26
|
+
console.log(chalk.cyan('━'.repeat(60)));
|
|
27
|
+
console.log(chalk.cyan.bold('🤖 Artifact-Driven AI Agent Framework'));
|
|
28
|
+
console.log(chalk.cyan('━'.repeat(60)));
|
|
17
29
|
console.log('');
|
|
18
30
|
|
|
19
|
-
//
|
|
31
|
+
// 1. 세팅 확인
|
|
20
32
|
if (!isWorkspaceSetup()) {
|
|
21
33
|
console.log(chalk.yellow('⚠️ 프로젝트가 세팅되지 않았습니다.'));
|
|
22
34
|
console.log('');
|
|
@@ -25,7 +37,7 @@ export async function interactive() {
|
|
|
25
37
|
{
|
|
26
38
|
type: 'confirm',
|
|
27
39
|
name: 'doSetup',
|
|
28
|
-
message: '지금 세팅하시겠습니까?',
|
|
40
|
+
message: '지금 프로젝트를 세팅하시겠습니까?',
|
|
29
41
|
default: true
|
|
30
42
|
}
|
|
31
43
|
]);
|
|
@@ -34,58 +46,337 @@ export async function interactive() {
|
|
|
34
46
|
await setup();
|
|
35
47
|
} else {
|
|
36
48
|
console.log(chalk.gray('나중에 `ada setup`으로 세팅할 수 있습니다.'));
|
|
37
|
-
return;
|
|
38
49
|
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 2. 메인 메뉴 루프
|
|
54
|
+
while (true) {
|
|
55
|
+
const { action } = await inquirer.prompt([
|
|
56
|
+
{
|
|
57
|
+
type: 'list',
|
|
58
|
+
name: 'action',
|
|
59
|
+
message: '작업을 선택하세요:',
|
|
60
|
+
pageSize: 12,
|
|
61
|
+
choices: [
|
|
62
|
+
new inquirer.Separator('── 핵심 기능 ──'),
|
|
63
|
+
{ name: '🤖 역할별 에이전트 실행 (설정 도구)', value: 'run' },
|
|
64
|
+
|
|
65
|
+
new inquirer.Separator('── 관리 기능 ──'),
|
|
66
|
+
{ name: '🏃 스프린트 관리 (Sprint)', value: 'sprint' },
|
|
67
|
+
{ name: '📊 상태 및 모니터링 (Status & Sessions)', value: 'monitor' },
|
|
68
|
+
{ name: '📝 문서 관리 (Docs)', value: 'docs' },
|
|
69
|
+
|
|
70
|
+
new inquirer.Separator('── 설정 및 기타 ──'),
|
|
71
|
+
{ name: '⚙️ 설정 (Config)', value: 'config' },
|
|
72
|
+
{ name: '🛠️ 프로젝트 관리 (Upgrade/Validate)', value: 'project' },
|
|
73
|
+
{ name: '❌ 종료', value: 'exit' }
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
if (action === 'exit') {
|
|
79
|
+
console.log(chalk.gray('안녕히 가세요! 👋'));
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
await handleMenuAction(action);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error(chalk.red(`❌ 오류 발생: ${err.message}`));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 작업 완료 후 줄바꿈
|
|
90
|
+
console.log('');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function handleMenuAction(action) {
|
|
95
|
+
switch (action) {
|
|
96
|
+
case 'run':
|
|
97
|
+
await handleRunAgent();
|
|
98
|
+
break;
|
|
99
|
+
|
|
100
|
+
case 'sprint':
|
|
101
|
+
await handleSprintMenu();
|
|
102
|
+
break;
|
|
103
|
+
|
|
104
|
+
case 'monitor':
|
|
105
|
+
await handleMonitorMenu();
|
|
106
|
+
break;
|
|
107
|
+
|
|
108
|
+
case 'docs':
|
|
109
|
+
await handleDocsMenu();
|
|
110
|
+
break;
|
|
111
|
+
|
|
112
|
+
case 'config':
|
|
113
|
+
await config();
|
|
114
|
+
break;
|
|
115
|
+
|
|
116
|
+
case 'project':
|
|
117
|
+
await handleProjectMenu();
|
|
118
|
+
break;
|
|
39
119
|
}
|
|
120
|
+
}
|
|
40
121
|
|
|
41
|
-
|
|
122
|
+
/**
|
|
123
|
+
* 2. 에이전트 실행 메뉴
|
|
124
|
+
*/
|
|
125
|
+
async function handleRunAgent() {
|
|
42
126
|
const roles = getAvailableRoles();
|
|
43
127
|
const tools = getAvailableTools();
|
|
44
128
|
|
|
45
129
|
if (roles.length === 0) {
|
|
46
130
|
console.log(chalk.red('❌ 사용 가능한 역할이 없습니다.'));
|
|
47
|
-
console.log(chalk.gray('`ada setup`으로 다시 세팅하세요.'));
|
|
48
131
|
return;
|
|
49
132
|
}
|
|
50
133
|
|
|
51
|
-
const
|
|
134
|
+
const roleChoices = roles.map(r => ({
|
|
135
|
+
name: `${getRoleDescription(r)} (설정: ${getToolForRole(r)})`,
|
|
136
|
+
value: r
|
|
137
|
+
}));
|
|
138
|
+
roleChoices.push(new inquirer.Separator());
|
|
139
|
+
roleChoices.push({ name: '🔙 뒤로가기', value: null });
|
|
140
|
+
|
|
141
|
+
const { role } = await inquirer.prompt([
|
|
52
142
|
{
|
|
53
143
|
type: 'list',
|
|
54
144
|
name: 'role',
|
|
55
|
-
message: '역할을 선택하세요:',
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
value: r
|
|
59
|
-
}))
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
type: 'list',
|
|
63
|
-
name: 'tool',
|
|
64
|
-
message: 'AI 도구를 선택하세요:',
|
|
65
|
-
choices: tools.map(t => ({
|
|
66
|
-
name: getToolDescription(t),
|
|
67
|
-
value: t
|
|
68
|
-
}))
|
|
145
|
+
message: '실행할 역할을 선택하세요:',
|
|
146
|
+
pageSize: 10,
|
|
147
|
+
choices: roleChoices
|
|
69
148
|
}
|
|
70
149
|
]);
|
|
71
150
|
|
|
72
|
-
|
|
151
|
+
if (!role) return;
|
|
152
|
+
|
|
153
|
+
const configuredTool = getToolForRole(role);
|
|
154
|
+
let selectedTool = configuredTool;
|
|
155
|
+
|
|
156
|
+
if (!tools.includes(configuredTool)) {
|
|
157
|
+
console.log(chalk.yellow(`⚠️ 설정된 도구(${configuredTool})가 지원 목록에 없습니다.`));
|
|
158
|
+
const { tool } = await inquirer.prompt([
|
|
159
|
+
{
|
|
160
|
+
type: 'list',
|
|
161
|
+
name: 'tool',
|
|
162
|
+
message: '사용할 AI 도구를 선택하세요:',
|
|
163
|
+
choices: tools.map(t => ({
|
|
164
|
+
name: getToolDescription(t),
|
|
165
|
+
value: t
|
|
166
|
+
}))
|
|
167
|
+
}
|
|
168
|
+
]);
|
|
169
|
+
selectedTool = tool;
|
|
170
|
+
} else {
|
|
171
|
+
const { runMode } = await inquirer.prompt([
|
|
172
|
+
{
|
|
173
|
+
type: 'list',
|
|
174
|
+
name: 'runMode',
|
|
175
|
+
message: `선택된 도구: ${configuredTool}. 어떻게 실행할까요?`,
|
|
176
|
+
choices: [
|
|
177
|
+
{ name: `바로 실행 (${configuredTool})`, value: 'configured' },
|
|
178
|
+
{ name: '도구 변경 후 실행', value: 'manual' },
|
|
179
|
+
{ name: '🔙 뒤로가기', value: 'back' }
|
|
180
|
+
]
|
|
181
|
+
}
|
|
182
|
+
]);
|
|
183
|
+
|
|
184
|
+
if (runMode === 'back') return;
|
|
185
|
+
|
|
186
|
+
if (runMode === 'manual') {
|
|
187
|
+
const { tool } = await inquirer.prompt([
|
|
188
|
+
{
|
|
189
|
+
type: 'list',
|
|
190
|
+
name: 'tool',
|
|
191
|
+
message: 'AI 도구를 선택하세요:',
|
|
192
|
+
choices: tools.map(t => ({
|
|
193
|
+
name: getToolDescription(t),
|
|
194
|
+
value: t
|
|
195
|
+
})),
|
|
196
|
+
default: configuredTool
|
|
197
|
+
}
|
|
198
|
+
]);
|
|
199
|
+
selectedTool = tool;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.log('');
|
|
204
|
+
await run(role, selectedTool);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 3. 스프린트 메뉴
|
|
209
|
+
*/
|
|
210
|
+
async function handleSprintMenu() {
|
|
211
|
+
const { subAction } = await inquirer.prompt([{
|
|
212
|
+
type: 'list',
|
|
213
|
+
name: 'subAction',
|
|
214
|
+
message: '스프린트 작업:',
|
|
215
|
+
choices: [
|
|
216
|
+
{ name: '🆕 스프린트 생성 (Create)', value: 'create' },
|
|
217
|
+
{ name: '➕ Task 추가 (Add)', value: 'add' },
|
|
218
|
+
{ name: '📋 목록 보기 (List)', value: 'list' },
|
|
219
|
+
{ name: '🔄 동기화 (Sync Meta)', value: 'sync' },
|
|
220
|
+
{ name: '🚪 스프린트 종료 (Close)', value: 'close' },
|
|
221
|
+
{ name: '🔙 뒤로가기', value: 'back' }
|
|
222
|
+
]
|
|
223
|
+
}]);
|
|
224
|
+
|
|
225
|
+
if (subAction === 'back') return;
|
|
226
|
+
|
|
227
|
+
// 스프린트 명령어 호출 (인자 처리 필요)
|
|
228
|
+
if (subAction === 'add') {
|
|
229
|
+
const { taskIds } = await inquirer.prompt([{
|
|
230
|
+
type: 'input',
|
|
231
|
+
name: 'taskIds',
|
|
232
|
+
message: '추가할 Task ID를 공백으로 구분하여 입력하세요 (예: task-001 task-002):'
|
|
233
|
+
}]);
|
|
234
|
+
if (taskIds.trim()) {
|
|
235
|
+
await sprintCommand('add', taskIds.split(' '));
|
|
236
|
+
}
|
|
237
|
+
} else if (subAction === 'close') {
|
|
238
|
+
const { closeOpt } = await inquirer.prompt([{
|
|
239
|
+
type: 'list',
|
|
240
|
+
name: 'closeOpt',
|
|
241
|
+
message: '종료 옵션:',
|
|
242
|
+
choices: [
|
|
243
|
+
{ name: '기본 (작업파일 보관)', value: [] },
|
|
244
|
+
{ name: '정리 (작업파일 삭제)', value: ['--clean'] },
|
|
245
|
+
{ name: '유지 (모든파일 유지)', value: ['--keep-all'] }
|
|
246
|
+
]
|
|
247
|
+
}]);
|
|
248
|
+
// close는 내부적으로 commander program 객체 구조에 의존할 수 있으므로 직접 호출 시 주의 필요
|
|
249
|
+
// 여기서는 cli.js를 통하지 않고 직접 함수 호출하므로, sprintCommand 구현 확인 필요
|
|
250
|
+
// sprintCommand는 (action, tasks, options) 시그니처를 가짐 (commander action wrapper)
|
|
251
|
+
// 하지만 commander action은 (arg1, arg2..., options) 형태임.
|
|
252
|
+
|
|
253
|
+
// 단순화를 위해 여기서는 sprint.js의 로직을 직접 호출하는게 좋지만,
|
|
254
|
+
// 현재 구조상 sprintCommand는 commander action 핸들러임.
|
|
255
|
+
// 임시로 옵션 객체 흉내내서 전달
|
|
256
|
+
const options = {};
|
|
257
|
+
if (closeOpt.includes('--clean')) options.clean = true;
|
|
258
|
+
if (closeOpt.includes('--keep-all')) options.keepAll = true;
|
|
259
|
+
|
|
260
|
+
await sprintCommand('close', [], options);
|
|
261
|
+
|
|
262
|
+
} else {
|
|
263
|
+
await sprintCommand(subAction, []);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 5. 모니터링 메뉴
|
|
269
|
+
*/
|
|
270
|
+
async function handleMonitorMenu() {
|
|
271
|
+
const { subAction } = await inquirer.prompt([{
|
|
272
|
+
type: 'list',
|
|
273
|
+
name: 'subAction',
|
|
274
|
+
message: '모니터링:',
|
|
275
|
+
choices: [
|
|
276
|
+
{ name: '📊 전체 상태 확인 (Status)', value: 'status' },
|
|
277
|
+
{ name: '🖥️ 실시간 대시보드 (Dashboard)', value: 'dashboard' },
|
|
278
|
+
{ name: '📋 세션 목록 (Sessions)', value: 'sessions' },
|
|
279
|
+
{ name: '📜 최근 로그 (Logs)', value: 'logs' },
|
|
280
|
+
{ name: '🔙 뒤로가기', value: 'back' }
|
|
281
|
+
]
|
|
282
|
+
}]);
|
|
283
|
+
|
|
284
|
+
if (subAction === 'back') return;
|
|
285
|
+
|
|
286
|
+
if (subAction === 'status') await status();
|
|
287
|
+
else if (subAction === 'dashboard') await monitor();
|
|
288
|
+
else if (subAction === 'sessions') await handleSessionsMenu();
|
|
289
|
+
else if (subAction === 'logs') await logs();
|
|
73
290
|
}
|
|
74
291
|
|
|
292
|
+
/**
|
|
293
|
+
* 5-1. 세션 메뉴
|
|
294
|
+
*/
|
|
295
|
+
async function handleSessionsMenu() {
|
|
296
|
+
await sessions({});
|
|
297
|
+
|
|
298
|
+
const { nextAction } = await inquirer.prompt([{
|
|
299
|
+
type: 'list',
|
|
300
|
+
name: 'nextAction',
|
|
301
|
+
message: '세션 작업:',
|
|
302
|
+
choices: [
|
|
303
|
+
{ name: '🧹 세션 정리 (Clean)', value: 'clean' },
|
|
304
|
+
{ name: '🔙 뒤로가기', value: 'back' }
|
|
305
|
+
]
|
|
306
|
+
}]);
|
|
307
|
+
|
|
308
|
+
if (nextAction === 'clean') {
|
|
309
|
+
await sessions({ clean: true });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* 6. 문서 메뉴
|
|
315
|
+
*/
|
|
316
|
+
async function handleDocsMenu() {
|
|
317
|
+
const { subAction } = await inquirer.prompt([{
|
|
318
|
+
type: 'list',
|
|
319
|
+
name: 'subAction',
|
|
320
|
+
message: '문서 작업:',
|
|
321
|
+
choices: [
|
|
322
|
+
{ name: '🏗️ 문서 사이트 초기화 (Init)', value: 'init' },
|
|
323
|
+
{ name: '📄 문서 생성/갱신 (Generate)', value: 'generate' },
|
|
324
|
+
{ name: '🌐 로컬 미리보기 (Serve)', value: 'serve' },
|
|
325
|
+
{ name: '🚀 배포 (Publish)', value: 'publish' },
|
|
326
|
+
{ name: '🔙 뒤로가기', value: 'back' }
|
|
327
|
+
]
|
|
328
|
+
}]);
|
|
329
|
+
|
|
330
|
+
if (subAction === 'back') return;
|
|
331
|
+
|
|
332
|
+
// docsCommand도 commander action 핸들러임
|
|
333
|
+
if (subAction === 'init') {
|
|
334
|
+
const { generator } = await inquirer.prompt([{
|
|
335
|
+
type: 'list',
|
|
336
|
+
name: 'generator',
|
|
337
|
+
message: '생성기 선택:',
|
|
338
|
+
choices: ['mkdocs', 'jekyll']
|
|
339
|
+
}]);
|
|
340
|
+
await docsCommand('init', { generator });
|
|
341
|
+
} else {
|
|
342
|
+
await docsCommand(subAction, {});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* 8. 프로젝트 관리 메뉴
|
|
348
|
+
*/
|
|
349
|
+
async function handleProjectMenu() {
|
|
350
|
+
const { subAction } = await inquirer.prompt([{
|
|
351
|
+
type: 'list',
|
|
352
|
+
name: 'subAction',
|
|
353
|
+
message: '프로젝트 관리:',
|
|
354
|
+
choices: [
|
|
355
|
+
{ name: '✅ 문서 검증 (Validate)', value: 'validate' },
|
|
356
|
+
{ name: '⬆️ 업그레이드 (Upgrade)', value: 'upgrade' },
|
|
357
|
+
{ name: '🔙 뒤로가기', value: 'back' }
|
|
358
|
+
]
|
|
359
|
+
}]);
|
|
360
|
+
|
|
361
|
+
if (subAction === 'back') return;
|
|
362
|
+
|
|
363
|
+
if (subAction === 'validate') await validate();
|
|
364
|
+
else if (subAction === 'upgrade') await upgrade({});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// --- Helpers ---
|
|
368
|
+
|
|
75
369
|
function getRoleDescription(role) {
|
|
76
370
|
const descriptions = {
|
|
77
371
|
planner: 'planner - 요구사항 수집, Task 분해',
|
|
78
|
-
|
|
372
|
+
improver: 'improver - 기존 기능 개선 기획',
|
|
79
373
|
developer: 'developer - 코드 구현 (범용)',
|
|
80
374
|
reviewer: 'reviewer - 코드 리뷰',
|
|
81
|
-
|
|
82
|
-
|
|
375
|
+
documenter: 'documenter - 문서 작성',
|
|
376
|
+
analyzer: 'analyzer - 코드베이스 분석',
|
|
377
|
+
manager: 'manager - (수동) 프로젝트 관리',
|
|
83
378
|
backend: 'backend - API 설계, 서버 구현',
|
|
84
379
|
frontend: 'frontend - UI 구현, API 연동',
|
|
85
|
-
'library-developer': 'library-dev - 공개 API 설계, 버전 관리',
|
|
86
|
-
'game-logic': 'game-logic - 게임 시스템 설계',
|
|
87
|
-
rendering: 'rendering - 화면/이펙트 구현',
|
|
88
|
-
'cli-developer': 'cli-dev - 명령어 설계, 출력 형식'
|
|
89
380
|
};
|
|
90
381
|
return descriptions[role] || role;
|
|
91
382
|
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { gatherProjectState, renderDashboard } from '../ui/dashboard.js';
|
|
5
|
+
import { KeyHandler, waitForKey, isTTY } from '../ui/keyHandler.js';
|
|
6
|
+
import { executeQuickAction } from '../ui/quickActions.js';
|
|
7
|
+
import { isWorkspaceSetup, getWorkspaceDir } from '../utils/files.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 대시보드 자동 갱신 주기 (밀리초)
|
|
11
|
+
*/
|
|
12
|
+
const REFRESH_INTERVAL = 2000;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 파일 감시 디바운스 시간 (밀리초)
|
|
16
|
+
*/
|
|
17
|
+
const DEBOUNCE_TIME = 500;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* [CLI] 모니터 명령어 핸들러
|
|
21
|
+
* 실시간 대시보드 및 빠른 명령어 제공
|
|
22
|
+
*/
|
|
23
|
+
export async function monitor() {
|
|
24
|
+
// TTY 확인
|
|
25
|
+
if (!isTTY()) {
|
|
26
|
+
console.log(chalk.yellow('UI 모드는 터미널에서만 사용할 수 있습니다.'));
|
|
27
|
+
console.log(chalk.gray('일반 상태 확인: ada status'));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Setup 확인
|
|
32
|
+
if (!isWorkspaceSetup()) {
|
|
33
|
+
console.log(chalk.red('먼저 setup을 실행하세요.'));
|
|
34
|
+
console.log(chalk.gray(' ada setup <template>'));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 상태 변수
|
|
39
|
+
let currentState = null;
|
|
40
|
+
let statusMessage = '시작 중...';
|
|
41
|
+
let isRunningAction = false;
|
|
42
|
+
let refreshTimer = null;
|
|
43
|
+
let fileWatcher = null;
|
|
44
|
+
let debounceTimer = null;
|
|
45
|
+
const canAutoRefresh = process.stdout.isTTY;
|
|
46
|
+
|
|
47
|
+
// 키 핸들러 초기화
|
|
48
|
+
const keyHandler = new KeyHandler({
|
|
49
|
+
onKey: async (key) => {
|
|
50
|
+
if (isRunningAction) return;
|
|
51
|
+
|
|
52
|
+
isRunningAction = true;
|
|
53
|
+
keyHandler.pause();
|
|
54
|
+
|
|
55
|
+
// 자동 갱신 일시 중지
|
|
56
|
+
if (refreshTimer) {
|
|
57
|
+
clearInterval(refreshTimer);
|
|
58
|
+
refreshTimer = null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
statusMessage = '실행 중...';
|
|
63
|
+
const result = await executeQuickAction(key, currentState);
|
|
64
|
+
|
|
65
|
+
if (result.error) {
|
|
66
|
+
statusMessage = `오류: ${result.error}`;
|
|
67
|
+
} else {
|
|
68
|
+
statusMessage = '준비됨';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 완료 후 대기 (목록 표시 후 키 대기가 필요한 명령어들)
|
|
72
|
+
const waitKeys = ['h', 's', 'l', 't'];
|
|
73
|
+
if (waitKeys.includes(key)) {
|
|
74
|
+
await waitForKey();
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
statusMessage = `오류: ${error.message}`;
|
|
78
|
+
} finally {
|
|
79
|
+
isRunningAction = false;
|
|
80
|
+
keyHandler.resume();
|
|
81
|
+
|
|
82
|
+
// 자동 갱신 재시작
|
|
83
|
+
startAutoRefresh();
|
|
84
|
+
|
|
85
|
+
// 화면 갱신
|
|
86
|
+
refresh();
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
onRefresh: () => {
|
|
90
|
+
if (!isRunningAction) {
|
|
91
|
+
refresh();
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
onQuit: () => {
|
|
95
|
+
cleanup();
|
|
96
|
+
console.log(chalk.gray('\nUI 모드를 종료합니다.\n'));
|
|
97
|
+
process.exit(0);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 화면 새로고침
|
|
103
|
+
*/
|
|
104
|
+
function refresh() {
|
|
105
|
+
try {
|
|
106
|
+
currentState = gatherProjectState();
|
|
107
|
+
renderDashboard(currentState, statusMessage);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error(chalk.red(`화면 갱신 오류: ${error.message}`));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 자동 갱신 시작
|
|
115
|
+
*/
|
|
116
|
+
function startAutoRefresh() {
|
|
117
|
+
if (!canAutoRefresh) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (refreshTimer) {
|
|
122
|
+
clearInterval(refreshTimer);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
refreshTimer = setInterval(() => {
|
|
126
|
+
if (!isRunningAction) {
|
|
127
|
+
refresh();
|
|
128
|
+
}
|
|
129
|
+
}, REFRESH_INTERVAL);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 파일 감시 시작 (스프린트/Task 변경 감지)
|
|
134
|
+
*/
|
|
135
|
+
function startFileWatcher() {
|
|
136
|
+
if (!canAutoRefresh) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const workspace = getWorkspaceDir();
|
|
142
|
+
const watchPaths = [
|
|
143
|
+
path.join(workspace, 'artifacts', 'sprints'),
|
|
144
|
+
path.join(workspace, '.ada-status.json')
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
watchPaths.forEach(watchPath => {
|
|
148
|
+
if (fs.existsSync(watchPath)) {
|
|
149
|
+
try {
|
|
150
|
+
const watcher = fs.watch(watchPath, { recursive: true }, (eventType, filename) => {
|
|
151
|
+
// 디바운스 처리
|
|
152
|
+
if (debounceTimer) {
|
|
153
|
+
clearTimeout(debounceTimer);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
debounceTimer = setTimeout(() => {
|
|
157
|
+
if (!isRunningAction) {
|
|
158
|
+
statusMessage = '파일 변경 감지됨';
|
|
159
|
+
refresh();
|
|
160
|
+
setTimeout(() => {
|
|
161
|
+
statusMessage = '준비됨';
|
|
162
|
+
}, 1000);
|
|
163
|
+
}
|
|
164
|
+
}, DEBOUNCE_TIME);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// 첫 번째 watcher만 저장 (정리용)
|
|
168
|
+
if (!fileWatcher) {
|
|
169
|
+
fileWatcher = watcher;
|
|
170
|
+
}
|
|
171
|
+
} catch (e) {
|
|
172
|
+
// 파일 감시 실패는 무시 (일부 플랫폼에서 지원 안됨)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
} catch (error) {
|
|
177
|
+
// 파일 감시 실패는 조용히 무시
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 정리
|
|
183
|
+
*/
|
|
184
|
+
function cleanup() {
|
|
185
|
+
if (refreshTimer) {
|
|
186
|
+
clearInterval(refreshTimer);
|
|
187
|
+
}
|
|
188
|
+
if (fileWatcher) {
|
|
189
|
+
fileWatcher.close();
|
|
190
|
+
}
|
|
191
|
+
if (debounceTimer) {
|
|
192
|
+
clearTimeout(debounceTimer);
|
|
193
|
+
}
|
|
194
|
+
keyHandler.stop();
|
|
195
|
+
|
|
196
|
+
// 커서 표시
|
|
197
|
+
process.stdout.write('\x1b[?25h');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 프로세스 종료 시 정리
|
|
201
|
+
process.on('SIGINT', () => {
|
|
202
|
+
cleanup();
|
|
203
|
+
process.exit(0);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
process.on('SIGTERM', () => {
|
|
207
|
+
cleanup();
|
|
208
|
+
process.exit(0);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// 터미널 타이틀 설정
|
|
212
|
+
process.stdout.write('\x1b]0;ADA UI Mode\x07');
|
|
213
|
+
|
|
214
|
+
// 커서 숨기기
|
|
215
|
+
process.stdout.write('\x1b[?25l');
|
|
216
|
+
|
|
217
|
+
// 초기 렌더링
|
|
218
|
+
statusMessage = canAutoRefresh
|
|
219
|
+
? '준비됨'
|
|
220
|
+
: '자동 갱신 비활성화됨 (TTY 미지원)';
|
|
221
|
+
refresh();
|
|
222
|
+
|
|
223
|
+
// 키 핸들러 시작
|
|
224
|
+
keyHandler.start();
|
|
225
|
+
|
|
226
|
+
// 자동 갱신 시작
|
|
227
|
+
startAutoRefresh();
|
|
228
|
+
|
|
229
|
+
// 파일 감시 시작
|
|
230
|
+
startFileWatcher();
|
|
231
|
+
|
|
232
|
+
// 무한 대기 (이벤트 루프 유지)
|
|
233
|
+
await new Promise(() => {});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export default monitor;
|