@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.
Files changed (80) hide show
  1. package/README.md +709 -516
  2. package/ai-dev-team/.ada-status.json +10 -0
  3. package/ai-dev-team/.ada-version +6 -0
  4. package/ai-dev-team/.current-template +1 -0
  5. package/ai-dev-team/.sessions/logs/20260124-014551-00f04724.log +5 -0
  6. package/ai-dev-team/.sessions/logs/20260124-014623-cb2b1d44.log +5 -0
  7. package/ai-dev-team/ada.config.json +15 -0
  8. package/ai-dev-team/artifacts/api.md +212 -0
  9. package/ai-dev-team/artifacts/decision.md +72 -0
  10. package/ai-dev-team/artifacts/improvement-reports/IMP-0000-template.md +57 -0
  11. package/ai-dev-team/artifacts/plan.md +187 -0
  12. package/ai-dev-team/artifacts/project.md +193 -0
  13. package/ai-dev-team/artifacts/sprints/_template/docs/release-notes.md +37 -0
  14. package/ai-dev-team/artifacts/sprints/_template/meta.md +54 -0
  15. package/ai-dev-team/artifacts/sprints/_template/retrospective.md +50 -0
  16. package/ai-dev-team/artifacts/sprints/_template/review-reports/review-template.md +49 -0
  17. package/ai-dev-team/artifacts/sprints/_template/tasks/task-template.md +43 -0
  18. package/ai-dev-team/artifacts/ui.md +104 -0
  19. package/ai-dev-team/roles/analyzer.md +265 -0
  20. package/ai-dev-team/roles/developer.md +222 -0
  21. package/ai-dev-team/roles/documenter.md +715 -0
  22. package/ai-dev-team/roles/improver.md +461 -0
  23. package/ai-dev-team/roles/manager.md +544 -0
  24. package/ai-dev-team/roles/planner.md +398 -0
  25. package/ai-dev-team/roles/reviewer.md +294 -0
  26. package/ai-dev-team/rules/api-change.md +198 -0
  27. package/ai-dev-team/rules/document-priority.md +199 -0
  28. package/ai-dev-team/rules/escalation.md +172 -0
  29. package/ai-dev-team/rules/iteration.md +236 -0
  30. package/ai-dev-team/rules/rfc.md +31 -0
  31. package/ai-dev-team/rules/rollback.md +218 -0
  32. package/bin/cli.js +49 -5
  33. package/core/artifacts/sprints/_template/meta.md +4 -4
  34. package/core/docs-templates/mkdocs/docs/architecture/overview.md +29 -0
  35. package/core/docs-templates/mkdocs/docs/changelog.md +36 -0
  36. package/core/docs-templates/mkdocs/docs/contributing/contributing.md +60 -0
  37. package/core/docs-templates/mkdocs/docs/getting-started/configuration.md +51 -0
  38. package/core/docs-templates/mkdocs/docs/getting-started/installation.md +41 -0
  39. package/core/docs-templates/mkdocs/docs/getting-started/quick-start.md +56 -0
  40. package/core/docs-templates/mkdocs/docs/guides/api-reference.md +83 -0
  41. package/core/docs-templates/mkdocs/docs/index.md +32 -0
  42. package/core/docs-templates/mkdocs/mkdocs.yml +86 -0
  43. package/core/roles/analyzer.md +32 -10
  44. package/core/roles/developer.md +222 -223
  45. package/core/roles/documenter.md +592 -170
  46. package/core/roles/improver.md +461 -0
  47. package/core/roles/manager.md +4 -1
  48. package/core/roles/planner.md +160 -10
  49. package/core/roles/reviewer.md +31 -3
  50. package/core/rules/document-priority.md +2 -1
  51. package/core/rules/rollback.md +3 -3
  52. package/package.json +1 -1
  53. package/src/commands/config.js +371 -0
  54. package/src/commands/docs.js +502 -0
  55. package/src/commands/interactive.js +324 -33
  56. package/src/commands/monitor.js +236 -0
  57. package/src/commands/run.js +360 -122
  58. package/src/commands/sessions.js +270 -70
  59. package/src/commands/setup.js +22 -1
  60. package/src/commands/sprint.js +295 -54
  61. package/src/commands/status.js +34 -1
  62. package/src/commands/upgrade.js +416 -0
  63. package/src/commands/validate.js +4 -3
  64. package/src/index.js +1 -0
  65. package/src/ui/dashboard.js +518 -0
  66. package/src/ui/keyHandler.js +147 -0
  67. package/src/ui/quickActions.js +111 -0
  68. package/src/utils/config.js +74 -0
  69. package/src/utils/files.js +70 -3
  70. package/src/utils/sessionState.js +472 -328
  71. package/src/utils/sessionState.process.test.js +101 -0
  72. package/src/utils/sessionState.test.js +183 -0
  73. package/src/utils/sprintUtils.js +134 -0
  74. package/src/utils/taskParser.js +134 -0
  75. package/src/utils/taskParser.test.js +76 -0
  76. package/ai-dev-team/artifacts/features/_template/qa.md +0 -16
  77. package/examples/todo-app/README.md +0 -23
  78. package/examples/todo-app/artifacts/backlog.md +0 -23
  79. package/examples/todo-app/artifacts/plan.md +0 -23
  80. package/examples/todo-app/artifacts/project.md +0 -23
@@ -3,11 +3,18 @@ import path from 'path';
3
3
  import chalk from 'chalk';
4
4
  import readline from 'readline';
5
5
  import { getSessionsDir, isWorkspaceSetup, getWorkspaceDir } from '../utils/files.js';
6
- import { getActiveSessions, getPendingQuestions, readStatus, getStatusFilePath, cleanupZombieSessions } from '../utils/sessionState.js';
6
+ import {
7
+ getActiveSessions,
8
+ getPendingQuestions,
9
+ readStatus,
10
+ getStatusFilePath,
11
+ cleanupZombieSessions,
12
+ answerQuestion
13
+ } from '../utils/sessionState.js';
7
14
 
8
15
  export async function sessions(options = {}) {
9
16
  if (!isWorkspaceSetup()) {
10
- console.log(chalk.red('❌ λ¨Όμ € setup을 μ‹€ν–‰ν•˜μ„Έμš”.'));
17
+ console.log(chalk.red('? λ¨Όμ € setup을 μ‹€ν–‰ν•˜μ„Έμš”.'));
11
18
  process.exit(1);
12
19
  }
13
20
 
@@ -16,26 +23,31 @@ export async function sessions(options = {}) {
16
23
  return watchSessions();
17
24
  }
18
25
 
26
+ // Clean λͺ¨λ“œ
27
+ if (options.clean) {
28
+ return cleanupCompletedSessions();
29
+ }
30
+
19
31
  const sessionsDir = getSessionsDir();
20
32
 
21
- // μ’€λΉ„ μ„Έμ…˜ 정리 (60λΆ„ 이상 된 μ„Έμ…˜)
33
+ // μ’€λΉ„ μ„Έμ…˜ 정리 (λΉ„ν™œμ„±/였래된 μ„Έμ…˜)
22
34
  const removedCount = cleanupZombieSessions(60);
23
35
 
24
36
  console.log('');
25
37
  console.log(chalk.cyan('━'.repeat(60)));
26
- console.log(chalk.cyan.bold('πŸ“‹ μ„Έμ…˜ μƒνƒœ'));
38
+ console.log(chalk.cyan.bold('μ„Έμ…˜ μƒνƒœ'));
27
39
  console.log(chalk.cyan('━'.repeat(60)));
28
40
  console.log('');
29
41
 
30
42
  if (removedCount > 0) {
31
- console.log(chalk.yellow(`🧹 μ’€λΉ„ μ„Έμ…˜ ${removedCount}개 정리됨 (60λΆ„ 이상 κ²½κ³Ό)`));
43
+ console.log(chalk.yellow(`μ’€λΉ„ μ„Έμ…˜ ${removedCount}개 정리됨 (λΉ„ν™œμ„±/였래됨)`));
32
44
  console.log('');
33
45
  }
34
46
 
35
47
  // 1. μ‹€μ‹œκ°„ ν™œμ„± μ„Έμ…˜ ν‘œμ‹œ
36
48
  const activeSessions = getActiveSessions();
37
49
  if (activeSessions.length > 0) {
38
- console.log(chalk.yellow.bold('🟒 ν™œμ„± μ„Έμ…˜ (μ‹€μ‹œκ°„)'));
50
+ console.log(chalk.yellow.bold('ν™œμ„± μ„Έμ…˜ (μ‹€μ‹œκ°„)'));
39
51
  console.log('');
40
52
  console.log(chalk.gray(' μ—­ν•  도ꡬ μ‹œμž‘ μ‹œκ°„ μƒνƒœ'));
41
53
  console.log(chalk.gray(' ' + '─'.repeat(56)));
@@ -45,7 +57,7 @@ export async function sessions(options = {}) {
45
57
  const tool = (session.tool || '-').padEnd(8);
46
58
  const startTime = new Date(session.startedAt).toLocaleString('ko-KR');
47
59
  const status = session.status || 'active';
48
- const statusIcon = status === 'active' ? '🟒' : '🟑';
60
+ const statusIcon = status === 'active' ? '+' : '-';
49
61
 
50
62
  console.log(` ${role} ${tool} ${startTime} ${statusIcon} ${status}`);
51
63
  });
@@ -58,7 +70,7 @@ export async function sessions(options = {}) {
58
70
  // 2. λŒ€κΈ° 쀑인 질문 ν‘œμ‹œ
59
71
  const pendingQuestions = getPendingQuestions();
60
72
  if (pendingQuestions.length > 0) {
61
- console.log(chalk.yellow.bold('⚠️ λŒ€κΈ° 질문'));
73
+ console.log(chalk.yellow.bold('λŒ€κΈ° 질문'));
62
74
  console.log('');
63
75
 
64
76
  pendingQuestions.forEach(q => {
@@ -74,18 +86,23 @@ export async function sessions(options = {}) {
74
86
  // 3. Task μ§„ν–‰ 상황 ν‘œμ‹œ
75
87
  const status = readStatus();
76
88
  const taskProgress = status.taskProgress || {};
77
- const activeTasks = Object.entries(taskProgress).filter(([_, info]) =>
78
- info.status && info.status !== 'DONE'
79
- );
89
+ const activeTasks = Object.entries(taskProgress).filter(([_, info]) => {
90
+ const s = (info.status || '').toUpperCase();
91
+ return s && s !== 'DONE' && s !== 'REJECTED';
92
+ });
80
93
 
81
94
  if (activeTasks.length > 0) {
82
- console.log(chalk.cyan.bold('πŸ“Š μ§„ν–‰ 쀑인 Task'));
95
+ console.log(chalk.cyan.bold('μ§„ν–‰ 쀑인 Task'));
83
96
  console.log('');
84
97
 
85
98
  activeTasks.forEach(([taskId, info]) => {
86
- const progress = info.progress || 0;
87
- const progressBar = 'β–ˆ'.repeat(Math.floor(progress / 10)) + 'β–‘'.repeat(10 - Math.floor(progress / 10));
88
- console.log(` ${taskId}: ${progressBar} ${progress}% (${info.status})`);
99
+ const normalizedStatus = info.status === 'IN_QA' ? 'IN_REVIEW' : info.status;
100
+ const statusColor = normalizedStatus === 'DONE' ? chalk.green :
101
+ normalizedStatus === 'IN_DEV' ? chalk.yellow :
102
+ normalizedStatus === 'IN_REVIEW' ? chalk.blue :
103
+ chalk.white;
104
+
105
+ console.log(` ${taskId}: [${statusColor(normalizedStatus)}]`);
89
106
  if (info.assignee) {
90
107
  console.log(chalk.gray(` λ‹΄λ‹Ή: ${info.assignee}`));
91
108
  }
@@ -117,7 +134,7 @@ export async function sessions(options = {}) {
117
134
  return;
118
135
  }
119
136
 
120
- console.log(chalk.cyan.bold('πŸ“œ 졜근 μ„Έμ…˜ 기둝'));
137
+ console.log(chalk.cyan.bold('졜근 μ„Έμ…˜ 기둝'));
121
138
  console.log('');
122
139
  console.log(chalk.gray(' μ„Έμ…˜ ID μ—­ν•  도ꡬ μƒνƒœ'));
123
140
  console.log(chalk.gray(' ' + '─'.repeat(56)));
@@ -159,11 +176,124 @@ async function watchSessions() {
159
176
  const statusFile = getStatusFilePath();
160
177
  let lastUpdate = '';
161
178
  let isWatching = true;
179
+ let isPrompting = false;
180
+ const promptQueue = [];
181
+ const promptedQuestions = new Set();
182
+
183
+ function pauseWatch() {
184
+ isWatching = false;
185
+ }
186
+
187
+ function resumeWatch() {
188
+ isWatching = true;
189
+ drawScreen();
190
+ lastUpdate = new Date().toLocaleTimeString('ko-KR');
191
+ }
162
192
 
163
- // ν‚€λ³΄λ“œ μž…λ ₯ μ„€μ •
164
- if (process.stdin.isTTY) {
165
- readline.emitKeypressEvents(process.stdin);
166
- process.stdin.setRawMode(true);
193
+ function disableKeypressHandling() {
194
+ if (process.stdin.isTTY) {
195
+ process.stdin.setRawMode(false);
196
+ }
197
+ process.stdin.removeListener('keypress', keyHandler);
198
+ }
199
+
200
+ function enableKeypressHandling() {
201
+ if (process.stdin.isTTY) {
202
+ readline.emitKeypressEvents(process.stdin);
203
+ process.stdin.setRawMode(true);
204
+ }
205
+ process.stdin.on('keypress', keyHandler);
206
+ }
207
+
208
+ function enqueueQuestions(questions) {
209
+ questions.forEach(question => {
210
+ if (!promptedQuestions.has(question.id)) {
211
+ promptQueue.push(question);
212
+ promptedQuestions.add(question.id);
213
+ }
214
+ });
215
+ }
216
+
217
+ async function processPromptQueue() {
218
+ if (isPrompting || promptQueue.length === 0) {
219
+ return;
220
+ }
221
+
222
+ isPrompting = true;
223
+ pauseWatch();
224
+ disableKeypressHandling();
225
+
226
+ while (promptQueue.length > 0) {
227
+ const nextQuestion = promptQueue.shift();
228
+ await promptQuestion(nextQuestion);
229
+ }
230
+
231
+ enableKeypressHandling();
232
+ isPrompting = false;
233
+ resumeWatch();
234
+ }
235
+
236
+ async function promptQuestion(question) {
237
+ const status = readStatus();
238
+ const currentQuestion = status.pendingQuestions?.find(q => q.id === question.id);
239
+
240
+ if (!currentQuestion || currentQuestion.status !== 'waiting') {
241
+ return;
242
+ }
243
+
244
+ console.log('');
245
+ console.log(chalk.yellow('━'.repeat(60)));
246
+ console.log(chalk.yellow.bold('질문 응닡 ν•„μš”'));
247
+ console.log(chalk.yellow('━'.repeat(60)));
248
+ console.log(chalk.white(` ID: ${currentQuestion.id}`));
249
+ console.log(chalk.white(` μš”μ²­: ${currentQuestion.from} β†’ ${currentQuestion.to}`));
250
+ console.log(chalk.white(` 질문: ${currentQuestion.question}`));
251
+
252
+ if (currentQuestion.options && currentQuestion.options.length > 0) {
253
+ console.log(chalk.gray(' μ˜΅μ…˜:'));
254
+ currentQuestion.options.forEach((option, index) => {
255
+ console.log(chalk.gray(` ${index + 1}) ${option}`));
256
+ });
257
+ }
258
+
259
+ const promptText = currentQuestion.options && currentQuestion.options.length > 0
260
+ ? ' λ‹΅λ³€(번호 λ˜λŠ” 직접 μž…λ ₯): '
261
+ : ' λ‹΅λ³€: ';
262
+
263
+ const answerInput = await askInput(chalk.cyan(promptText));
264
+ let answer = (answerInput || '').trim();
265
+
266
+ if (currentQuestion.options && currentQuestion.options.length > 0) {
267
+ const optionIndex = Number.parseInt(answer, 10);
268
+ if (!Number.isNaN(optionIndex) && optionIndex >= 1 && optionIndex <= currentQuestion.options.length) {
269
+ answer = currentQuestion.options[optionIndex - 1];
270
+ }
271
+ }
272
+
273
+ if (!answer) {
274
+ answer = '(응닡 μ—†μŒ)';
275
+ }
276
+
277
+ answerQuestion(currentQuestion.id, answer);
278
+
279
+ // 처리 μ™„λ£Œ λ©”μ‹œμ§€
280
+ console.log(chalk.green(` βœ“ μ €μž₯ μ™„λ£Œ: ${currentQuestion.id}`));
281
+ console.log(chalk.gray(` βœ“ κ΄€λ ¨ μ•Œλ¦Ό 읽음 처리됨`));
282
+ console.log('');
283
+ }
284
+
285
+ function askInput(prompt) {
286
+ return new Promise(resolve => {
287
+ const rl = readline.createInterface({
288
+ input: process.stdin,
289
+ output: process.stdout
290
+ });
291
+
292
+ rl.question(prompt, answer => {
293
+ rl.close();
294
+ resolve(answer);
295
+ });
296
+ });
167
297
  }
168
298
 
169
299
  // ν™”λ©΄ 그리기 ν•¨μˆ˜
@@ -172,24 +302,25 @@ async function watchSessions() {
172
302
 
173
303
  const now = new Date();
174
304
  const timeString = now.toLocaleTimeString('ko-KR');
305
+ let pendingQuestions = [];
175
306
 
176
307
  // 헀더
177
308
  console.log('');
178
309
  console.log(chalk.cyan('β”Œ' + '─'.repeat(78) + '┐'));
179
- console.log(chalk.cyan('β”‚') + chalk.bold.white(' πŸ“‘ Manager Watch Mode'.padEnd(78)) + chalk.cyan('β”‚'));
180
- console.log(chalk.cyan('β”‚') + chalk.gray(` ⏰ ${timeString}`.padEnd(78)) + chalk.cyan('β”‚'));
310
+ console.log(chalk.cyan('β”‚') + chalk.bold.white(' Manager Watch Mode'.padEnd(78)) + chalk.cyan('β”‚'));
311
+ console.log(chalk.cyan('β”‚') + chalk.gray(` μ‹œκ°„: ${timeString}`.padEnd(78)) + chalk.cyan('β”‚'));
181
312
  console.log(chalk.cyan('β””' + '─'.repeat(78) + 'β”˜'));
182
313
  console.log('');
183
314
 
184
315
  try {
185
316
  const status = readStatus();
186
317
  const activeSessions = status.activeSessions || [];
187
- const pendingQuestions = status.pendingQuestions?.filter(q => q.status === 'waiting') || [];
318
+ pendingQuestions = status.pendingQuestions?.filter(q => q.status === 'waiting') || [];
188
319
  const taskProgress = status.taskProgress || {};
189
320
  const notifications = status.notifications || [];
190
321
 
191
322
  // 톡계 νŒ¨λ„
192
- console.log(chalk.bgBlue.white.bold(' πŸ“Š 톡계 '));
323
+ console.log(chalk.bgBlue.white.bold(' 톡계 '));
193
324
  console.log('');
194
325
  console.log(chalk.white(` ν™œμ„± μ„Έμ…˜: ${chalk.yellow(activeSessions.length)}개`));
195
326
  console.log(chalk.white(` λŒ€κΈ° 질문: ${pendingQuestions.length > 0 ? chalk.red(pendingQuestions.length) : chalk.green('0')}개`));
@@ -203,13 +334,13 @@ async function watchSessions() {
203
334
 
204
335
  // ν™œμ„± μ„Έμ…˜
205
336
  if (activeSessions.length > 0) {
206
- console.log(chalk.bgGreen.black.bold(' 🟒 ν™œμ„± μ„Έμ…˜ '));
337
+ console.log(chalk.bgGreen.black.bold(' ν™œμ„± μ„Έμ…˜ '));
207
338
  console.log('');
208
339
 
209
340
  activeSessions.forEach((session, index) => {
210
341
  const startTime = new Date(session.startedAt);
211
342
  const duration = Math.floor((now - startTime) / 1000 / 60); // λΆ„
212
- const statusIcon = session.status === 'active' ? '🟒' : '🟑';
343
+ const statusIcon = session.status === 'active' ? '+' : '-';
213
344
 
214
345
  console.log(chalk.white(` ${index + 1}. ${statusIcon} ${chalk.bold(session.role)}`));
215
346
  console.log(chalk.gray(` 도ꡬ: ${session.tool}`));
@@ -227,7 +358,7 @@ async function watchSessions() {
227
358
 
228
359
  // λŒ€κΈ° 질문 (κ°•μ‘°)
229
360
  if (pendingQuestions.length > 0) {
230
- console.log(chalk.bgYellow.black.bold(' ⚠️ λŒ€κΈ° 질문 '));
361
+ console.log(chalk.bgYellow.black.bold(' λŒ€κΈ° 질문 '));
231
362
  console.log('');
232
363
 
233
364
  pendingQuestions.slice(0, 3).forEach((q, index) => {
@@ -255,30 +386,29 @@ async function watchSessions() {
255
386
 
256
387
  // μ§„ν–‰ 쀑인 Task
257
388
  if (activeTasks.length > 0) {
258
- console.log(chalk.bgCyan.black.bold(' πŸ“Š μ§„ν–‰ 쀑인 Task '));
389
+ console.log(chalk.bgCyan.black.bold(' μ§„ν–‰ 쀑인 Task '));
259
390
  console.log('');
260
391
 
261
392
  activeTasks.slice(0, 5).forEach((taskId, index) => {
262
393
  const task = taskProgress[taskId];
263
- const progress = task.progress || 0;
264
- const bars = Math.floor(progress / 5);
265
- const progressBar = 'β–ˆ'.repeat(bars) + 'β–‘'.repeat(20 - bars);
266
-
394
+
267
395
  const statusColors = {
268
396
  'IN_DEV': chalk.blue,
269
397
  'IN_REVIEW': chalk.yellow,
270
- 'IN_QA': chalk.magenta,
271
398
  'READY': chalk.gray,
272
399
  'IN_SPRINT': chalk.cyan
273
400
  };
274
401
 
275
- const statusColor = statusColors[task.status] || chalk.white;
402
+ const normalizedStatus = task.status === 'IN_QA' ? 'IN_REVIEW' : task.status;
403
+ const statusColor = statusColors[normalizedStatus] || chalk.white;
276
404
 
277
- console.log(chalk.white(` ${taskId}: ${progressBar} ${progress}%`));
278
- console.log(chalk.gray(` μƒνƒœ: ${statusColor(task.status)} ${task.assignee ? `| λ‹΄λ‹Ή: ${task.assignee}` : ''}`));
405
+ console.log(chalk.white(` ${taskId}: [${statusColor(normalizedStatus)}]`));
406
+ if (task.assignee) {
407
+ console.log(chalk.gray(` λ‹΄λ‹Ή: ${task.assignee}`));
408
+ }
279
409
 
280
410
  if (task.note) {
281
- console.log(chalk.gray(` λ©”λͺ¨: ${task.note}`));
411
+ console.log(chalk.gray(` λ©”λͺ¨: ${task.note}`));
282
412
  }
283
413
 
284
414
  if (index < Math.min(activeTasks.length, 5) - 1) {
@@ -296,19 +426,19 @@ async function watchSessions() {
296
426
  // 졜근 μ•Œλ¦Ό
297
427
  const recentNotifications = notifications.slice(-3).reverse();
298
428
  if (recentNotifications.length > 0) {
299
- console.log(chalk.bgMagenta.white.bold(' πŸ”” 졜근 μ•Œλ¦Ό '));
429
+ console.log(chalk.bgMagenta.white.bold(' 졜근 μ•Œλ¦Ό '));
300
430
  console.log('');
301
431
 
302
432
  recentNotifications.forEach((notif, index) => {
303
433
  const typeIcons = {
304
- 'info': 'ℹ️',
305
- 'warning': '⚠️',
306
- 'error': '❌',
307
- 'question': '❓',
308
- 'complete': 'βœ…'
434
+ 'info': 'i',
435
+ 'warning': '!',
436
+ 'error': 'x',
437
+ 'question': '?',
438
+ 'complete': 'v'
309
439
  };
310
440
 
311
- const icon = typeIcons[notif.type] || 'ℹ️';
441
+ const icon = typeIcons[notif.type] || '?';
312
442
  const readStatus = notif.read ? chalk.gray('[읽음]') : chalk.yellow('[μ•ˆμ½μŒ]');
313
443
 
314
444
  console.log(chalk.white(` ${icon} ${readStatus} ${notif.message}`));
@@ -335,8 +465,30 @@ async function watchSessions() {
335
465
  if (lastUpdate) {
336
466
  console.log(chalk.gray(` λ§ˆμ§€λ§‰ μ—…λ°μ΄νŠΈ: ${lastUpdate}`));
337
467
  }
468
+
469
+ enqueueQuestions(pendingQuestions);
470
+ processPromptQueue();
338
471
  }
339
472
 
473
+ // ν‚€λ³΄λ“œ μž…λ ₯ 처리
474
+ const keyHandler = (str, key) => {
475
+ if (key.ctrl && key.name === 'c') {
476
+ cleanup();
477
+ } else if (key.name === 'q') {
478
+ cleanup();
479
+ } else if (key.name === 'r') {
480
+ drawScreen();
481
+ lastUpdate = new Date().toLocaleTimeString('ko-KR');
482
+ } else if (key.name === 'c') {
483
+ console.clear();
484
+ drawScreen();
485
+ } else if (key.name === 'h') {
486
+ showHelp();
487
+ }
488
+ };
489
+
490
+ enableKeypressHandling();
491
+
340
492
  // 초기 ν™”λ©΄ 그리기
341
493
  drawScreen();
342
494
  lastUpdate = new Date().toLocaleTimeString('ko-KR');
@@ -361,7 +513,7 @@ async function watchSessions() {
361
513
 
362
514
  // 30μ΄ˆλ§ˆλ‹€ μ’€λΉ„ μ„Έμ…˜ 정리 (2초 Γ— 15 = 30초)
363
515
  if (tickCount % 15 === 0) {
364
- const removedCount = cleanupZombieSessions(60); // 60λΆ„ 이상 된 μ„Έμ…˜ 제거
516
+ const removedCount = cleanupZombieSessions(60); // λΉ„ν™œμ„±/였래된 μ„Έμ…˜ 제거
365
517
  if (removedCount > 0) {
366
518
  lastUpdate = `${new Date().toLocaleTimeString('ko-KR')} (μ’€λΉ„ μ„Έμ…˜ ${removedCount}개 정리됨)`;
367
519
  }
@@ -371,38 +523,16 @@ async function watchSessions() {
371
523
  }
372
524
  }, 2000);
373
525
 
374
- // ν‚€λ³΄λ“œ μž…λ ₯ 처리
375
- const keyHandler = (str, key) => {
376
- if (key.ctrl && key.name === 'c') {
377
- cleanup();
378
- } else if (key.name === 'q') {
379
- cleanup();
380
- } else if (key.name === 'r') {
381
- drawScreen();
382
- lastUpdate = new Date().toLocaleTimeString('ko-KR');
383
- } else if (key.name === 'c') {
384
- console.clear();
385
- drawScreen();
386
- } else if (key.name === 'h') {
387
- showHelp();
388
- }
389
- };
390
-
391
- process.stdin.on('keypress', keyHandler);
392
-
393
526
  // 정리 ν•¨μˆ˜
394
527
  function cleanup() {
395
528
  isWatching = false;
396
529
  clearInterval(intervalId);
397
530
  if (watcher) watcher.close();
398
531
 
399
- if (process.stdin.isTTY) {
400
- process.stdin.setRawMode(false);
401
- }
402
- process.stdin.removeListener('keypress', keyHandler);
532
+ disableKeypressHandling();
403
533
 
404
534
  console.log('');
405
- console.log(chalk.cyan('πŸ‘‹ Watch λͺ¨λ“œλ₯Ό μ’…λ£Œν•©λ‹ˆλ‹€.'));
535
+ console.log(chalk.cyan('Watch λͺ¨λ“œλ₯Ό μ’…λ£Œν•©λ‹ˆλ‹€.'));
406
536
  console.log('');
407
537
  process.exit(0);
408
538
  }
@@ -412,7 +542,7 @@ async function watchSessions() {
412
542
  console.clear();
413
543
  console.log('');
414
544
  console.log(chalk.cyan('β”Œ' + '─'.repeat(78) + '┐'));
415
- console.log(chalk.cyan('β”‚') + chalk.bold.white(' πŸ“– Watch λͺ¨λ“œ 도움말'.padEnd(78)) + chalk.cyan('β”‚'));
545
+ console.log(chalk.cyan('β”‚') + chalk.bold.white(' Watch λͺ¨λ“œ 도움말'.padEnd(78)) + chalk.cyan('β”‚'));
416
546
  console.log(chalk.cyan('β””' + '─'.repeat(78) + 'β”˜'));
417
547
  console.log('');
418
548
  console.log(chalk.white(' Watch λͺ¨λ“œλŠ” μ‹€μ‹œκ°„μœΌλ‘œ μ„Έμ…˜ μƒνƒœλ₯Ό λͺ¨λ‹ˆν„°λ§ν•©λ‹ˆλ‹€.'));
@@ -430,6 +560,7 @@ async function watchSessions() {
430
560
  console.log('');
431
561
  console.log(chalk.yellow(' Manager μ—­ν• :'));
432
562
  console.log(chalk.white(' - λŒ€κΈ° 질문 확인 및 응닡'));
563
+ console.log(chalk.white(' - 질문 λ°œμƒ μ‹œ μžλ™ 응닡 ν”„λ‘¬ν”„νŠΈ ν‘œμ‹œ'));
433
564
  console.log(chalk.white(' - Task μ§„ν–‰ 상황 λͺ¨λ‹ˆν„°λ§'));
434
565
  console.log(chalk.white(' - μ„Έμ…˜ μƒνƒœ μ‹€μ‹œκ°„ 좔적'));
435
566
  console.log('');
@@ -444,3 +575,72 @@ async function watchSessions() {
444
575
  process.on('SIGINT', cleanup);
445
576
  process.on('SIGTERM', cleanup);
446
577
  }
578
+
579
+ /**
580
+ * μ™„λ£Œλœ μ„Έμ…˜ 정리
581
+ */
582
+ async function cleanupCompletedSessions() {
583
+ const sessionsDir = getSessionsDir();
584
+
585
+ if (!fs.existsSync(sessionsDir)) {
586
+ console.log(chalk.yellow('⚠️ μ„Έμ…˜ 디렉토리가 μ—†μŠ΅λ‹ˆλ‹€.'));
587
+ return;
588
+ }
589
+
590
+ console.log('');
591
+ console.log(chalk.cyan('━'.repeat(60)));
592
+ console.log(chalk.cyan.bold('μ™„λ£Œλœ μ„Έμ…˜ 정리 (status: completed)'));
593
+ console.log(chalk.cyan('━'.repeat(60)));
594
+ console.log('');
595
+
596
+ const sessionDirs = fs.readdirSync(sessionsDir)
597
+ .filter(f => fs.statSync(path.join(sessionsDir, f)).isDirectory())
598
+ .sort();
599
+
600
+ let completedCount = 0;
601
+ let skippedCount = 0;
602
+ let errorCount = 0;
603
+
604
+ for (const sessionId of sessionDirs) {
605
+ const sessionPath = path.join(sessionsDir, sessionId);
606
+ const sessionFile = path.join(sessionPath, 'session.json');
607
+
608
+ try {
609
+ // session.json 확인
610
+ if (!fs.existsSync(sessionFile)) {
611
+ console.log(chalk.gray(` ${sessionId}: session.json μ—†μŒ (κ±΄λ„ˆλœ€)`));
612
+ skippedCount++;
613
+ continue;
614
+ }
615
+
616
+ // μ„Έμ…˜ 정보 읽기
617
+ const session = JSON.parse(fs.readFileSync(sessionFile, 'utf-8'));
618
+
619
+ // μ™„λ£Œ μƒνƒœκ°€ μ•„λ‹ˆλ©΄ κ±΄λ„ˆλœ€
620
+ if (session.status !== 'completed') {
621
+ console.log(chalk.yellow(` ${sessionId}: μ™„λ£Œ μ•„λ‹˜ (${session.status || 'unknown'}) - μœ μ§€`));
622
+ skippedCount++;
623
+ continue;
624
+ }
625
+
626
+ // μ„Έμ…˜ 디렉토리 μ‚­μ œ
627
+ fs.removeSync(sessionPath);
628
+ console.log(chalk.green(` βœ“ ${sessionId}: μ‚­μ œλ¨ (${session.status})`));
629
+ completedCount++;
630
+
631
+ } catch (error) {
632
+ console.log(chalk.red(` βœ— ${sessionId}: 였λ₯˜ - ${error.message}`));
633
+ errorCount++;
634
+ }
635
+ }
636
+
637
+ console.log('');
638
+ console.log(chalk.cyan('━'.repeat(60)));
639
+ console.log(chalk.white(` μ‚­μ œλ¨: ${chalk.green(completedCount)}개`));
640
+ console.log(chalk.white(` μœ μ§€λ¨: ${chalk.gray(skippedCount)}개`));
641
+ if (errorCount > 0) {
642
+ console.log(chalk.white(` 였λ₯˜: ${chalk.red(errorCount)}개`));
643
+ }
644
+ console.log(chalk.cyan('━'.repeat(60)));
645
+ console.log('');
646
+ }
@@ -7,7 +7,9 @@ import {
7
7
  getWorkspaceDir,
8
8
  getCurrentTemplateFile,
9
9
  getAvailableTemplates,
10
- copyDirMerge
10
+ copyDirMerge,
11
+ getPackageVersion,
12
+ writeVersion
11
13
  } from '../utils/files.js';
12
14
 
13
15
  export async function setup(template) {
@@ -66,8 +68,11 @@ export async function setup(template) {
66
68
  fs.ensureDirSync(path.join(workspace, 'roles'));
67
69
  fs.ensureDirSync(path.join(workspace, 'artifacts'));
68
70
  fs.ensureDirSync(path.join(workspace, 'rules'));
71
+ fs.ensureDirSync(path.join(workspace, 'artifacts', 'backlog'));
72
+ fs.ensureDirSync(path.join(workspace, 'artifacts', 'sprints'));
69
73
  fs.ensureDirSync(path.join(workspace, 'artifacts', 'features', '_template'));
70
74
  fs.ensureDirSync(path.join(workspace, 'artifacts', 'rfc'));
75
+ fs.ensureDirSync(path.join(workspace, 'artifacts', 'improvement-reports'));
71
76
 
72
77
  // Core 볡사
73
78
  console.log(chalk.gray('πŸ“ Core 파일 볡사 쀑...'));
@@ -93,9 +98,25 @@ export async function setup(template) {
93
98
  fs.copyFileSync(rfcTemplateFile, path.join(workspace, 'artifacts', 'rfc', 'RFC-0000-template.md'));
94
99
  }
95
100
 
101
+ // Improvement Reports ν…œν”Œλ¦Ώ 볡사
102
+ const improvementTemplateFile = path.join(packageRoot, 'ai-dev-team', 'artifacts', 'improvement-reports', 'IMP-0000-template.md');
103
+ if (fs.existsSync(improvementTemplateFile)) {
104
+ fs.copyFileSync(improvementTemplateFile, path.join(workspace, 'artifacts', 'improvement-reports', 'IMP-0000-template.md'));
105
+ }
106
+
96
107
  // ν˜„μž¬ ν…œν”Œλ¦Ώ μ €μž₯
97
108
  fs.writeFileSync(getCurrentTemplateFile(), template);
98
109
 
110
+ // 버전 정보 μ €μž₯
111
+ const packageVersion = getPackageVersion();
112
+ const versionInfo = {
113
+ packageVersion: packageVersion,
114
+ workspaceVersion: packageVersion,
115
+ template: template,
116
+ lastUpgrade: new Date().toISOString()
117
+ };
118
+ writeVersion(versionInfo);
119
+
99
120
  // κ²°κ³Ό 좜λ ₯
100
121
  console.log('');
101
122
  console.log(chalk.green('βœ… μ„ΈνŒ… μ™„λ£Œ!'));