@silbaram/artifact-driven-agent 0.1.2 → 0.1.4

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.
@@ -1,70 +1,426 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import chalk from 'chalk';
4
- import { getSessionsDir, isWorkspaceSetup } from '../utils/files.js';
5
-
6
- export async function sessions() {
7
- if (!isWorkspaceSetup()) {
8
- console.log(chalk.red('❌ 먼저 setup을 실행하세요.'));
9
- process.exit(1);
10
- }
11
-
12
- const sessionsDir = getSessionsDir();
13
-
14
- console.log('');
15
- console.log(chalk.cyan('━'.repeat(60)));
16
- console.log(chalk.cyan.bold('📋 세션 목록'));
17
- console.log(chalk.cyan('━'.repeat(60)));
18
- console.log('');
19
-
20
- if (!fs.existsSync(sessionsDir)) {
21
- console.log(chalk.gray(' 세션 기록 없음'));
22
- console.log('');
23
- return;
24
- }
25
-
26
- const sessionDirs = fs.readdirSync(sessionsDir)
27
- .filter(f => fs.statSync(path.join(sessionsDir, f)).isDirectory())
28
- .sort()
29
- .reverse();
30
-
31
- if (sessionDirs.length === 0) {
32
- console.log(chalk.gray(' 세션 기록 없음'));
33
- console.log('');
34
- return;
35
- }
36
-
37
- // 헤더
38
- console.log(chalk.gray(' 세션 ID 역할 도구 상태'));
39
- console.log(chalk.gray(' ' + ''.repeat(56)));
40
-
41
- for (const sessionId of sessionDirs.slice(0, 20)) {
42
- const sessionFile = path.join(sessionsDir, sessionId, 'session.json');
43
-
44
- if (fs.existsSync(sessionFile)) {
45
- try {
46
- const session = JSON.parse(fs.readFileSync(sessionFile, 'utf-8'));
47
- const role = (session.role || '-').padEnd(10);
48
- const tool = (session.tool || '-').padEnd(8);
49
- const status = session.status || 'unknown';
50
-
51
- const statusColor = status === 'completed' ? chalk.green :
52
- status === 'active' ? chalk.yellow :
53
- chalk.gray;
54
-
55
- console.log(` ${sessionId} ${role} ${tool} ${statusColor(status)}`);
56
- } catch (e) {
57
- console.log(` ${sessionId} ${chalk.gray('(읽기 실패)')}`);
58
- }
59
- } else {
60
- console.log(` ${sessionId} ${chalk.gray('(정보 없음)')}`);
61
- }
62
- }
63
-
64
- console.log('');
65
-
66
- if (sessionDirs.length > 20) {
67
- console.log(chalk.gray(` ... ${sessionDirs.length - 20}개 세션`));
68
- console.log('');
69
- }
70
- }
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import readline from 'readline';
5
+ import { getSessionsDir, isWorkspaceSetup, getWorkspaceDir } from '../utils/files.js';
6
+ import { getActiveSessions, getPendingQuestions, readStatus, getStatusFilePath } from '../utils/sessionState.js';
7
+
8
+ export async function sessions(options = {}) {
9
+ if (!isWorkspaceSetup()) {
10
+ console.log(chalk.red('❌ 먼저 setup을 실행하세요.'));
11
+ process.exit(1);
12
+ }
13
+
14
+ // Watch 모드
15
+ if (options.watch) {
16
+ return watchSessions();
17
+ }
18
+
19
+ const sessionsDir = getSessionsDir();
20
+
21
+ console.log('');
22
+ console.log(chalk.cyan(''.repeat(60)));
23
+ console.log(chalk.cyan.bold('📋 세션 상태'));
24
+ console.log(chalk.cyan('━'.repeat(60)));
25
+ console.log('');
26
+
27
+ // 1. 실시간 활성 세션 표시
28
+ const activeSessions = getActiveSessions();
29
+ if (activeSessions.length > 0) {
30
+ console.log(chalk.yellow.bold('🟢 활성 세션 (실시간)'));
31
+ console.log('');
32
+ console.log(chalk.gray(' 역할 도구 시작 시간 상태'));
33
+ console.log(chalk.gray(' ' + '─'.repeat(56)));
34
+
35
+ activeSessions.forEach(session => {
36
+ const role = (session.role || '-').padEnd(10);
37
+ const tool = (session.tool || '-').padEnd(8);
38
+ const startTime = new Date(session.startedAt).toLocaleString('ko-KR');
39
+ const status = session.status || 'active';
40
+ const statusIcon = status === 'active' ? '🟢' : '🟡';
41
+
42
+ console.log(` ${role} ${tool} ${startTime} ${statusIcon} ${status}`);
43
+ });
44
+ console.log('');
45
+ } else {
46
+ console.log(chalk.gray(' 현재 활성 세션 없음'));
47
+ console.log('');
48
+ }
49
+
50
+ // 2. 대기 중인 질문 표시
51
+ const pendingQuestions = getPendingQuestions();
52
+ if (pendingQuestions.length > 0) {
53
+ console.log(chalk.yellow.bold('⚠️ 대기 질문'));
54
+ console.log('');
55
+
56
+ pendingQuestions.forEach(q => {
57
+ console.log(chalk.yellow(` [${q.id}] ${q.from} → ${q.to}`));
58
+ console.log(chalk.white(` 질문: ${q.question}`));
59
+ if (q.options && q.options.length > 0) {
60
+ console.log(chalk.gray(` 옵션: ${q.options.join(', ')}`));
61
+ }
62
+ console.log('');
63
+ });
64
+ }
65
+
66
+ // 3. Task 진행 상황 표시
67
+ const status = readStatus();
68
+ const taskProgress = status.taskProgress || {};
69
+ const activeTasks = Object.entries(taskProgress).filter(([_, info]) =>
70
+ info.status && info.status !== 'DONE'
71
+ );
72
+
73
+ if (activeTasks.length > 0) {
74
+ console.log(chalk.cyan.bold('📊 진행 중인 Task'));
75
+ console.log('');
76
+
77
+ activeTasks.forEach(([taskId, info]) => {
78
+ const progress = info.progress || 0;
79
+ const progressBar = '█'.repeat(Math.floor(progress / 10)) + '░'.repeat(10 - Math.floor(progress / 10));
80
+ console.log(` ${taskId}: ${progressBar} ${progress}% (${info.status})`);
81
+ if (info.assignee) {
82
+ console.log(chalk.gray(` 담당: ${info.assignee}`));
83
+ }
84
+ if (info.note) {
85
+ console.log(chalk.gray(` 메모: ${info.note}`));
86
+ }
87
+ });
88
+ console.log('');
89
+ }
90
+
91
+ console.log(chalk.cyan('━'.repeat(60)));
92
+ console.log('');
93
+
94
+ // 4. 최근 세션 기록 (히스토리)
95
+ if (!fs.existsSync(sessionsDir)) {
96
+ console.log(chalk.gray(' 세션 기록 없음'));
97
+ console.log('');
98
+ return;
99
+ }
100
+
101
+ const sessionDirs = fs.readdirSync(sessionsDir)
102
+ .filter(f => fs.statSync(path.join(sessionsDir, f)).isDirectory())
103
+ .sort()
104
+ .reverse();
105
+
106
+ if (sessionDirs.length === 0) {
107
+ console.log(chalk.gray(' 세션 기록 없음'));
108
+ console.log('');
109
+ return;
110
+ }
111
+
112
+ console.log(chalk.cyan.bold('📜 최근 세션 기록'));
113
+ console.log('');
114
+ console.log(chalk.gray(' 세션 ID 역할 도구 상태'));
115
+ console.log(chalk.gray(' ' + '─'.repeat(56)));
116
+
117
+ for (const sessionId of sessionDirs.slice(0, 10)) {
118
+ const sessionFile = path.join(sessionsDir, sessionId, 'session.json');
119
+
120
+ if (fs.existsSync(sessionFile)) {
121
+ try {
122
+ const session = JSON.parse(fs.readFileSync(sessionFile, 'utf-8'));
123
+ const role = (session.role || '-').padEnd(10);
124
+ const tool = (session.tool || '-').padEnd(8);
125
+ const status = session.status || 'unknown';
126
+
127
+ const statusColor = status === 'completed' ? chalk.green :
128
+ status === 'active' ? chalk.yellow :
129
+ status === 'error' ? chalk.red :
130
+ chalk.gray;
131
+
132
+ console.log(` ${sessionId} ${role} ${tool} ${statusColor(status)}`);
133
+ } catch (e) {
134
+ console.log(` ${sessionId} ${chalk.gray('(읽기 실패)')}`);
135
+ }
136
+ } else {
137
+ console.log(` ${sessionId} ${chalk.gray('(정보 없음)')}`);
138
+ }
139
+ }
140
+
141
+ console.log('');
142
+
143
+ if (sessionDirs.length > 10) {
144
+ console.log(chalk.gray(` ... 그 외 ${sessionDirs.length - 10}개 세션 (ada logs 명령어로 확인)`));
145
+ console.log('');
146
+ }
147
+ }
148
+
149
+ // Watch 모드: 실시간 세션 모니터링
150
+ async function watchSessions() {
151
+ const statusFile = getStatusFilePath();
152
+ let lastUpdate = '';
153
+ let isWatching = true;
154
+
155
+ // 키보드 입력 설정
156
+ if (process.stdin.isTTY) {
157
+ readline.emitKeypressEvents(process.stdin);
158
+ process.stdin.setRawMode(true);
159
+ }
160
+
161
+ // 화면 그리기 함수
162
+ function drawScreen() {
163
+ console.clear();
164
+
165
+ const now = new Date();
166
+ const timeString = now.toLocaleTimeString('ko-KR');
167
+
168
+ // 헤더
169
+ console.log('');
170
+ console.log(chalk.cyan('┌' + '─'.repeat(78) + '┐'));
171
+ console.log(chalk.cyan('│') + chalk.bold.white(' 📡 Manager Watch Mode'.padEnd(78)) + chalk.cyan('│'));
172
+ console.log(chalk.cyan('│') + chalk.gray(` ⏰ ${timeString}`.padEnd(78)) + chalk.cyan('│'));
173
+ console.log(chalk.cyan('└' + '─'.repeat(78) + '┘'));
174
+ console.log('');
175
+
176
+ try {
177
+ const status = readStatus();
178
+ const activeSessions = status.activeSessions || [];
179
+ const pendingQuestions = status.pendingQuestions?.filter(q => q.status === 'waiting') || [];
180
+ const taskProgress = status.taskProgress || {};
181
+ const notifications = status.notifications || [];
182
+
183
+ // 통계 패널
184
+ console.log(chalk.bgBlue.white.bold(' 📊 통계 '));
185
+ console.log('');
186
+ console.log(chalk.white(` 활성 세션: ${chalk.yellow(activeSessions.length)}개`));
187
+ console.log(chalk.white(` 대기 질문: ${pendingQuestions.length > 0 ? chalk.red(pendingQuestions.length) : chalk.green('0')}개`));
188
+
189
+ const activeTasks = Object.keys(taskProgress).filter(
190
+ taskId => taskProgress[taskId].status !== 'DONE'
191
+ );
192
+ console.log(chalk.white(` 진행 Task: ${chalk.cyan(activeTasks.length)}개`));
193
+ console.log(chalk.white(` 읽지 않은 알림: ${notifications.filter(n => !n.read).length}개`));
194
+ console.log('');
195
+
196
+ // 활성 세션
197
+ if (activeSessions.length > 0) {
198
+ console.log(chalk.bgGreen.black.bold(' 🟢 활성 세션 '));
199
+ console.log('');
200
+
201
+ activeSessions.forEach((session, index) => {
202
+ const startTime = new Date(session.startedAt);
203
+ const duration = Math.floor((now - startTime) / 1000 / 60); // 분
204
+ const statusIcon = session.status === 'active' ? '🟢' : '🟡';
205
+
206
+ console.log(chalk.white(` ${index + 1}. ${statusIcon} ${chalk.bold(session.role)}`));
207
+ console.log(chalk.gray(` 도구: ${session.tool}`));
208
+ console.log(chalk.gray(` 실행 시간: ${duration}분`));
209
+
210
+ if (index < activeSessions.length - 1) {
211
+ console.log('');
212
+ }
213
+ });
214
+ console.log('');
215
+ } else {
216
+ console.log(chalk.gray(' 현재 활성 세션이 없습니다.'));
217
+ console.log('');
218
+ }
219
+
220
+ // 대기 질문 (강조)
221
+ if (pendingQuestions.length > 0) {
222
+ console.log(chalk.bgYellow.black.bold(' ⚠️ 대기 질문 '));
223
+ console.log('');
224
+
225
+ pendingQuestions.slice(0, 3).forEach((q, index) => {
226
+ console.log(chalk.yellow(` [${q.id}] ${q.from} → ${q.to}`));
227
+ console.log(chalk.white(` 질문: ${q.question}`));
228
+
229
+ if (q.options && q.options.length > 0) {
230
+ console.log(chalk.gray(` 옵션: ${q.options.join(' / ')}`));
231
+ }
232
+
233
+ const elapsed = Math.floor((now - new Date(q.createdAt)) / 1000 / 60);
234
+ console.log(chalk.gray(` 대기 시간: ${elapsed}분`));
235
+
236
+ if (index < Math.min(pendingQuestions.length, 3) - 1) {
237
+ console.log('');
238
+ }
239
+ });
240
+
241
+ if (pendingQuestions.length > 3) {
242
+ console.log('');
243
+ console.log(chalk.gray(` ... 그 외 ${pendingQuestions.length - 3}개 질문`));
244
+ }
245
+ console.log('');
246
+ }
247
+
248
+ // 진행 중인 Task
249
+ if (activeTasks.length > 0) {
250
+ console.log(chalk.bgCyan.black.bold(' 📊 진행 중인 Task '));
251
+ console.log('');
252
+
253
+ activeTasks.slice(0, 5).forEach((taskId, index) => {
254
+ const task = taskProgress[taskId];
255
+ const progress = task.progress || 0;
256
+ const bars = Math.floor(progress / 5);
257
+ const progressBar = '█'.repeat(bars) + '░'.repeat(20 - bars);
258
+
259
+ const statusColors = {
260
+ 'IN_DEV': chalk.blue,
261
+ 'IN_REVIEW': chalk.yellow,
262
+ 'IN_QA': chalk.magenta,
263
+ 'READY': chalk.gray,
264
+ 'IN_SPRINT': chalk.cyan
265
+ };
266
+
267
+ const statusColor = statusColors[task.status] || chalk.white;
268
+
269
+ console.log(chalk.white(` ${taskId}: ${progressBar} ${progress}%`));
270
+ console.log(chalk.gray(` 상태: ${statusColor(task.status)} ${task.assignee ? `| 담당: ${task.assignee}` : ''}`));
271
+
272
+ if (task.note) {
273
+ console.log(chalk.gray(` 메모: ${task.note}`));
274
+ }
275
+
276
+ if (index < Math.min(activeTasks.length, 5) - 1) {
277
+ console.log('');
278
+ }
279
+ });
280
+
281
+ if (activeTasks.length > 5) {
282
+ console.log('');
283
+ console.log(chalk.gray(` ... 그 외 ${activeTasks.length - 5}개 Task`));
284
+ }
285
+ console.log('');
286
+ }
287
+
288
+ // 최근 알림
289
+ const recentNotifications = notifications.slice(-3).reverse();
290
+ if (recentNotifications.length > 0) {
291
+ console.log(chalk.bgMagenta.white.bold(' 🔔 최근 알림 '));
292
+ console.log('');
293
+
294
+ recentNotifications.forEach((notif, index) => {
295
+ const typeIcons = {
296
+ 'info': 'ℹ️',
297
+ 'warning': '⚠️',
298
+ 'error': '❌',
299
+ 'question': '❓',
300
+ 'complete': '✅'
301
+ };
302
+
303
+ const icon = typeIcons[notif.type] || 'ℹ️';
304
+ const readStatus = notif.read ? chalk.gray('[읽음]') : chalk.yellow('[안읽음]');
305
+
306
+ console.log(chalk.white(` ${icon} ${readStatus} ${notif.message}`));
307
+ console.log(chalk.gray(` from: ${notif.from}`));
308
+
309
+ if (index < recentNotifications.length - 1) {
310
+ console.log('');
311
+ }
312
+ });
313
+ console.log('');
314
+ }
315
+
316
+ } catch (error) {
317
+ console.log(chalk.red(' 상태 파일 읽기 오류'));
318
+ console.log(chalk.gray(` ${error.message}`));
319
+ console.log('');
320
+ }
321
+
322
+ // 푸터
323
+ console.log(chalk.cyan('─'.repeat(80)));
324
+ console.log(chalk.gray(' [q] 종료 [r] 새로고침 [c] 화면 지우기 [h] 도움말'));
325
+ console.log(chalk.cyan('─'.repeat(80)));
326
+
327
+ if (lastUpdate) {
328
+ console.log(chalk.gray(` 마지막 업데이트: ${lastUpdate}`));
329
+ }
330
+ }
331
+
332
+ // 초기 화면 그리기
333
+ drawScreen();
334
+ lastUpdate = new Date().toLocaleTimeString('ko-KR');
335
+
336
+ // 파일 감시
337
+ let watcher;
338
+ if (fs.existsSync(statusFile)) {
339
+ watcher = fs.watch(statusFile, (eventType) => {
340
+ if (eventType === 'change' && isWatching) {
341
+ drawScreen();
342
+ lastUpdate = new Date().toLocaleTimeString('ko-KR');
343
+ }
344
+ });
345
+ }
346
+
347
+ // 2초마다 화면 갱신 (시간 표시 업데이트)
348
+ const intervalId = setInterval(() => {
349
+ if (isWatching) {
350
+ drawScreen();
351
+ }
352
+ }, 2000);
353
+
354
+ // 키보드 입력 처리
355
+ const keyHandler = (str, key) => {
356
+ if (key.ctrl && key.name === 'c') {
357
+ cleanup();
358
+ } else if (key.name === 'q') {
359
+ cleanup();
360
+ } else if (key.name === 'r') {
361
+ drawScreen();
362
+ lastUpdate = new Date().toLocaleTimeString('ko-KR');
363
+ } else if (key.name === 'c') {
364
+ console.clear();
365
+ drawScreen();
366
+ } else if (key.name === 'h') {
367
+ showHelp();
368
+ }
369
+ };
370
+
371
+ process.stdin.on('keypress', keyHandler);
372
+
373
+ // 정리 함수
374
+ function cleanup() {
375
+ isWatching = false;
376
+ clearInterval(intervalId);
377
+ if (watcher) watcher.close();
378
+
379
+ if (process.stdin.isTTY) {
380
+ process.stdin.setRawMode(false);
381
+ }
382
+ process.stdin.removeListener('keypress', keyHandler);
383
+
384
+ console.log('');
385
+ console.log(chalk.cyan('👋 Watch 모드를 종료합니다.'));
386
+ console.log('');
387
+ process.exit(0);
388
+ }
389
+
390
+ // 도움말 표시
391
+ function showHelp() {
392
+ console.clear();
393
+ console.log('');
394
+ console.log(chalk.cyan('┌' + '─'.repeat(78) + '┐'));
395
+ console.log(chalk.cyan('│') + chalk.bold.white(' 📖 Watch 모드 도움말'.padEnd(78)) + chalk.cyan('│'));
396
+ console.log(chalk.cyan('└' + '─'.repeat(78) + '┘'));
397
+ console.log('');
398
+ console.log(chalk.white(' Watch 모드는 실시간으로 세션 상태를 모니터링합니다.'));
399
+ console.log('');
400
+ console.log(chalk.yellow(' 키보드 단축키:'));
401
+ console.log(chalk.white(' q - 종료'));
402
+ console.log(chalk.white(' r - 수동 새로고침'));
403
+ console.log(chalk.white(' c - 화면 지우기'));
404
+ console.log(chalk.white(' h - 이 도움말'));
405
+ console.log(chalk.white(' Ctrl+C - 강제 종료'));
406
+ console.log('');
407
+ console.log(chalk.yellow(' 자동 갱신:'));
408
+ console.log(chalk.white(' - .ada-status.json 파일 변경 시 즉시 갱신'));
409
+ console.log(chalk.white(' - 2초마다 시간 정보 자동 갱신'));
410
+ console.log('');
411
+ console.log(chalk.yellow(' Manager 역할:'));
412
+ console.log(chalk.white(' - 대기 질문 확인 및 응답'));
413
+ console.log(chalk.white(' - Task 진행 상황 모니터링'));
414
+ console.log(chalk.white(' - 세션 상태 실시간 추적'));
415
+ console.log('');
416
+ console.log(chalk.gray(' 아무 키나 눌러서 계속...'));
417
+
418
+ process.stdin.once('keypress', () => {
419
+ drawScreen();
420
+ });
421
+ }
422
+
423
+ // 프로세스 종료 처리
424
+ process.on('SIGINT', cleanup);
425
+ process.on('SIGTERM', cleanup);
426
+ }