@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/src/commands/sprint.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
-
import
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { getWorkspaceDir, isWorkspaceSetup } from '../utils/files.js';
|
|
6
|
+
import { syncSprint, findActiveSprint, updateSprintMeta } from '../utils/sprintUtils.js';
|
|
7
|
+
import { normalizeTaskStatus } from '../utils/taskParser.js';
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* 스프린트 관리 명령어
|
|
8
|
-
* @param {string} action - create / add / close / list
|
|
11
|
+
* @param {string} action - create / add / close / list / sync
|
|
9
12
|
* @param {Array} args - 추가 인자
|
|
10
13
|
*/
|
|
11
14
|
export default async function sprint(action, ...args) {
|
|
@@ -25,8 +28,11 @@ export default async function sprint(action, ...args) {
|
|
|
25
28
|
case 'add':
|
|
26
29
|
await addTasks(sprintsDir, args);
|
|
27
30
|
break;
|
|
31
|
+
case 'sync':
|
|
32
|
+
await syncSprint(sprintsDir);
|
|
33
|
+
break;
|
|
28
34
|
case 'close':
|
|
29
|
-
await closeSprint(sprintsDir);
|
|
35
|
+
await closeSprint(sprintsDir, args);
|
|
30
36
|
break;
|
|
31
37
|
case 'list':
|
|
32
38
|
await listSprints(sprintsDir);
|
|
@@ -35,10 +41,14 @@ export default async function sprint(action, ...args) {
|
|
|
35
41
|
console.log(chalk.red('❌ 알 수 없는 명령어입니다.'));
|
|
36
42
|
console.log('');
|
|
37
43
|
console.log(chalk.cyan('사용법:'));
|
|
38
|
-
console.log(chalk.gray(' ada sprint create
|
|
39
|
-
console.log(chalk.gray(' ada sprint add task-001 ...
|
|
40
|
-
console.log(chalk.gray(' ada sprint
|
|
41
|
-
console.log(chalk.gray(' ada sprint
|
|
44
|
+
console.log(chalk.gray(' ada sprint create - 새 스프린트 생성'));
|
|
45
|
+
console.log(chalk.gray(' ada sprint add task-001 ... - Task 추가'));
|
|
46
|
+
console.log(chalk.gray(' ada sprint sync - meta.md 상태 동기화'));
|
|
47
|
+
console.log(chalk.gray(' ada sprint close - 스프린트 종료 (작업 파일 archive)'));
|
|
48
|
+
console.log(chalk.gray(' ada sprint close --auto - 스프린트 자동 종료 (회고 기본값)'));
|
|
49
|
+
console.log(chalk.gray(' ada sprint close --clean - 스프린트 종료 (작업 파일 삭제)'));
|
|
50
|
+
console.log(chalk.gray(' ada sprint close --keep-all - 스프린트 종료 (파일 유지)'));
|
|
51
|
+
console.log(chalk.gray(' ada sprint list - 스프린트 목록'));
|
|
42
52
|
process.exit(1);
|
|
43
53
|
}
|
|
44
54
|
}
|
|
@@ -73,25 +83,37 @@ async function createSprint(sprintsDir) {
|
|
|
73
83
|
const sprintName = `sprint-${nextNumber}`;
|
|
74
84
|
const sprintPath = path.join(sprintsDir, sprintName);
|
|
75
85
|
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
process.exit(1);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
fs.copySync(templatePath, sprintPath);
|
|
86
|
+
// 스프린트 디렉토리 구조 생성
|
|
87
|
+
fs.ensureDirSync(path.join(sprintPath, 'tasks'));
|
|
88
|
+
fs.ensureDirSync(path.join(sprintPath, 'review-reports'));
|
|
89
|
+
fs.ensureDirSync(path.join(sprintPath, 'docs'));
|
|
84
90
|
|
|
85
|
-
// meta.md
|
|
91
|
+
// meta.md 생성 (간소화된 버전)
|
|
86
92
|
const metaPath = path.join(sprintPath, 'meta.md');
|
|
87
|
-
let metaContent = fs.readFileSync(metaPath, 'utf-8');
|
|
88
93
|
const today = new Date().toISOString().slice(0, 10);
|
|
89
94
|
|
|
90
|
-
metaContent =
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
const metaContent = `# Sprint ${nextNumber} 메타정보
|
|
96
|
+
|
|
97
|
+
| 항목 | 값 |
|
|
98
|
+
|------|-----|
|
|
99
|
+
| 스프린트 번호 | ${nextNumber} |
|
|
100
|
+
| 상태 | active |
|
|
101
|
+
| 시작일 | ${today} |
|
|
102
|
+
| 종료 예정 | TBD |
|
|
103
|
+
|
|
104
|
+
## Task 목록
|
|
105
|
+
|
|
106
|
+
> Task는 \`ada sprint add task-NNN\` 명령어로 추가됩니다.
|
|
107
|
+
> 추가된 Task는 아래에 자동으로 나열됩니다.
|
|
108
|
+
|
|
109
|
+
(Task 없음)
|
|
110
|
+
|
|
111
|
+
## 참고
|
|
112
|
+
|
|
113
|
+
- Task 상태: BACKLOG → IN_DEV → DONE
|
|
114
|
+
- 리뷰 결과: review-reports/ 참고
|
|
115
|
+
- 최종 문서: docs/ 참고
|
|
116
|
+
`;
|
|
95
117
|
|
|
96
118
|
fs.writeFileSync(metaPath, metaContent);
|
|
97
119
|
|
|
@@ -109,29 +131,6 @@ async function createSprint(sprintsDir) {
|
|
|
109
131
|
console.log('');
|
|
110
132
|
}
|
|
111
133
|
|
|
112
|
-
/**
|
|
113
|
-
* 현재 활성 스프린트 찾기
|
|
114
|
-
*/
|
|
115
|
-
function findActiveSprint(sprintsDir) {
|
|
116
|
-
if (!fs.existsSync(sprintsDir)) return null;
|
|
117
|
-
|
|
118
|
-
const sprints = fs.readdirSync(sprintsDir).filter(d => {
|
|
119
|
-
return fs.statSync(path.join(sprintsDir, d)).isDirectory() && !d.startsWith('_');
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
for (const sprint of sprints) {
|
|
123
|
-
const metaPath = path.join(sprintsDir, sprint, 'meta.md');
|
|
124
|
-
if (fs.existsSync(metaPath)) {
|
|
125
|
-
const content = fs.readFileSync(metaPath, 'utf-8');
|
|
126
|
-
if (content.includes('상태 | active')) {
|
|
127
|
-
return sprint;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
134
|
/**
|
|
136
135
|
* Task 추가
|
|
137
136
|
*/
|
|
@@ -158,7 +157,7 @@ async function addTasks(sprintsDir, taskIds) {
|
|
|
158
157
|
}
|
|
159
158
|
|
|
160
159
|
let addedCount = 0;
|
|
161
|
-
|
|
160
|
+
|
|
162
161
|
for (const taskId of taskIds) {
|
|
163
162
|
const taskFile = `${taskId}.md`;
|
|
164
163
|
const sourcePath = path.join(backlogPath, taskFile);
|
|
@@ -181,17 +180,20 @@ async function addTasks(sprintsDir, taskIds) {
|
|
|
181
180
|
console.log(chalk.green(`✅ ${taskId} 추가됨`));
|
|
182
181
|
}
|
|
183
182
|
|
|
183
|
+
// meta.md 업데이트 (Sync 호출)
|
|
184
|
+
await syncSprint(sprintsDir, true);
|
|
185
|
+
|
|
184
186
|
console.log('');
|
|
185
187
|
console.log(chalk.cyan(`📊 ${addedCount}개 Task가 ${activeSprint}에 추가되었습니다.`));
|
|
186
188
|
console.log('');
|
|
187
|
-
console.log(chalk.gray(` meta.md를 업데이트하여 Task 목록을 갱신하세요.`));
|
|
188
|
-
console.log('');
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
/**
|
|
192
192
|
* 스프린트 종료
|
|
193
|
+
* @param {string} sprintsDir - 스프린트 디렉토리
|
|
194
|
+
* @param {Array} args - 옵션 (--clean, --keep-all, --auto)
|
|
193
195
|
*/
|
|
194
|
-
async function closeSprint(sprintsDir) {
|
|
196
|
+
async function closeSprint(sprintsDir, args = []) {
|
|
195
197
|
const activeSprint = findActiveSprint(sprintsDir);
|
|
196
198
|
if (!activeSprint) {
|
|
197
199
|
console.log(chalk.red('❌ 활성 스프린트가 없습니다.'));
|
|
@@ -201,6 +203,14 @@ async function closeSprint(sprintsDir) {
|
|
|
201
203
|
const sprintPath = path.join(sprintsDir, activeSprint);
|
|
202
204
|
const metaPath = path.join(sprintPath, 'meta.md');
|
|
203
205
|
|
|
206
|
+
// 옵션 파싱
|
|
207
|
+
const hasClean = args.includes('--clean');
|
|
208
|
+
const hasKeepAll = args.includes('--keep-all');
|
|
209
|
+
const isAuto = args.includes('--auto');
|
|
210
|
+
|
|
211
|
+
// 종료 전 마지막 동기화
|
|
212
|
+
await syncSprint(sprintsDir, true);
|
|
213
|
+
|
|
204
214
|
// meta.md 업데이트 (active → completed)
|
|
205
215
|
let metaContent = fs.readFileSync(metaPath, 'utf-8');
|
|
206
216
|
const today = new Date().toISOString().slice(0, 10);
|
|
@@ -211,13 +221,93 @@ async function closeSprint(sprintsDir) {
|
|
|
211
221
|
|
|
212
222
|
fs.writeFileSync(metaPath, metaContent);
|
|
213
223
|
|
|
224
|
+
// retrospective.md 생성
|
|
214
225
|
console.log('');
|
|
215
|
-
console.log(chalk.
|
|
226
|
+
console.log(chalk.cyan('📝 스프린트 회고 작성'));
|
|
227
|
+
console.log(chalk.gray('━'.repeat(50)));
|
|
228
|
+
|
|
229
|
+
let retrospectiveData;
|
|
230
|
+
if (isAuto) {
|
|
231
|
+
console.log(chalk.gray('🤖 자동 모드: 기본값으로 회고 작성'));
|
|
232
|
+
retrospectiveData = await getRetrospectiveDataAuto(sprintPath);
|
|
233
|
+
} else {
|
|
234
|
+
retrospectiveData = await promptRetrospective(sprintPath);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
createRetrospective(sprintPath, activeSprint, today, retrospectiveData);
|
|
238
|
+
|
|
216
239
|
console.log('');
|
|
217
|
-
console.log(chalk.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
240
|
+
console.log(chalk.green('✅ 회고 작성 완료'));
|
|
241
|
+
|
|
242
|
+
// 작업 파일 정리
|
|
243
|
+
if (!hasKeepAll) {
|
|
244
|
+
const tasksDir = path.join(sprintPath, 'tasks');
|
|
245
|
+
const reviewReportsDir = path.join(sprintPath, 'review-reports');
|
|
246
|
+
|
|
247
|
+
if (hasClean) {
|
|
248
|
+
// --clean: 완전 삭제
|
|
249
|
+
console.log('');
|
|
250
|
+
console.log(chalk.yellow('🗑️ 작업 파일 삭제 중...'));
|
|
251
|
+
|
|
252
|
+
let deletedCount = 0;
|
|
253
|
+
if (fs.existsSync(tasksDir)) {
|
|
254
|
+
const taskFiles = fs.readdirSync(tasksDir).filter(f => !f.includes('template'));
|
|
255
|
+
taskFiles.forEach(f => fs.removeSync(path.join(tasksDir, f)));
|
|
256
|
+
deletedCount += taskFiles.length;
|
|
257
|
+
}
|
|
258
|
+
if (fs.existsSync(reviewReportsDir)) {
|
|
259
|
+
const reviewFiles = fs.readdirSync(reviewReportsDir).filter(f => !f.includes('template'));
|
|
260
|
+
reviewFiles.forEach(f => fs.removeSync(path.join(reviewReportsDir, f)));
|
|
261
|
+
deletedCount += reviewFiles.length;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
console.log(chalk.gray(` ✓ ${deletedCount}개 파일 삭제됨`));
|
|
265
|
+
} else {
|
|
266
|
+
// 기본: archive/ 폴더로 이동
|
|
267
|
+
console.log('');
|
|
268
|
+
console.log(chalk.cyan('📦 작업 파일 보관 중...'));
|
|
269
|
+
|
|
270
|
+
const archiveDir = path.join(sprintPath, 'archive');
|
|
271
|
+
fs.ensureDirSync(archiveDir);
|
|
272
|
+
|
|
273
|
+
let archivedCount = 0;
|
|
274
|
+
|
|
275
|
+
// tasks/ 이동
|
|
276
|
+
if (fs.existsSync(tasksDir)) {
|
|
277
|
+
const taskFiles = fs.readdirSync(tasksDir).filter(f => !f.includes('template'));
|
|
278
|
+
if (taskFiles.length > 0) {
|
|
279
|
+
const archiveTasksDir = path.join(archiveDir, 'tasks');
|
|
280
|
+
fs.ensureDirSync(archiveTasksDir);
|
|
281
|
+
taskFiles.forEach(f => {
|
|
282
|
+
fs.moveSync(path.join(tasksDir, f), path.join(archiveTasksDir, f), { overwrite: true });
|
|
283
|
+
});
|
|
284
|
+
archivedCount += taskFiles.length;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// review-reports/ 이동
|
|
289
|
+
if (fs.existsSync(reviewReportsDir)) {
|
|
290
|
+
const reviewFiles = fs.readdirSync(reviewReportsDir).filter(f => !f.includes('template'));
|
|
291
|
+
if (reviewFiles.length > 0) {
|
|
292
|
+
const archiveReviewsDir = path.join(archiveDir, 'review-reports');
|
|
293
|
+
fs.ensureDirSync(archiveReviewsDir);
|
|
294
|
+
reviewFiles.forEach(f => {
|
|
295
|
+
fs.moveSync(path.join(reviewReportsDir, f), path.join(archiveReviewsDir, f), { overwrite: true });
|
|
296
|
+
});
|
|
297
|
+
archivedCount += reviewFiles.length;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (archivedCount > 0) {
|
|
302
|
+
console.log(chalk.gray(` ✓ ${archivedCount}개 파일 → archive/`));
|
|
303
|
+
} else {
|
|
304
|
+
console.log(chalk.gray(` ✓ 정리할 파일 없음`));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
console.log('');
|
|
310
|
+
console.log(chalk.green(`✅ ${activeSprint}가 종료되었습니다!`));
|
|
221
311
|
console.log('');
|
|
222
312
|
}
|
|
223
313
|
|
|
@@ -260,3 +350,154 @@ async function listSprints(sprintsDir) {
|
|
|
260
350
|
|
|
261
351
|
console.log('');
|
|
262
352
|
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* 회고 데이터 자동 생성 (Auto Mode)
|
|
356
|
+
*/
|
|
357
|
+
async function getRetrospectiveDataAuto(sprintPath) {
|
|
358
|
+
const { completedTasks, incompleteTasks } = getTaskStatusForRetrospective(sprintPath);
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
completedTasks,
|
|
362
|
+
incompleteTasks,
|
|
363
|
+
keep: '자동 완료됨',
|
|
364
|
+
problem: '-',
|
|
365
|
+
try: '-'
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* 회고 작성 프롬프트
|
|
371
|
+
*/
|
|
372
|
+
async function promptRetrospective(sprintPath) {
|
|
373
|
+
const { completedTasks, incompleteTasks } = getTaskStatusForRetrospective(sprintPath);
|
|
374
|
+
|
|
375
|
+
console.log('');
|
|
376
|
+
console.log(chalk.white(`완료된 Task: ${completedTasks.length}개`));
|
|
377
|
+
console.log(chalk.white(`미완료 Task: ${incompleteTasks.length}개`));
|
|
378
|
+
console.log('');
|
|
379
|
+
|
|
380
|
+
const answers = await inquirer.prompt([
|
|
381
|
+
{
|
|
382
|
+
type: 'input',
|
|
383
|
+
name: 'keep',
|
|
384
|
+
message: '잘된 점 (Keep):',
|
|
385
|
+
default: '계획대로 진행됨'
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
type: 'input',
|
|
389
|
+
name: 'problem',
|
|
390
|
+
message: '개선할 점 (Problem):',
|
|
391
|
+
default: '-'
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
type: 'input',
|
|
395
|
+
name: 'try',
|
|
396
|
+
message: '시도할 것 (Try):',
|
|
397
|
+
default: '-'
|
|
398
|
+
}
|
|
399
|
+
]);
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
completedTasks,
|
|
403
|
+
incompleteTasks,
|
|
404
|
+
...answers
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* 회고를 위한 Task 상태 분류 헬퍼
|
|
410
|
+
*/
|
|
411
|
+
function getTaskStatusForRetrospective(sprintPath) {
|
|
412
|
+
// meta.md에서 Task 정보 읽기
|
|
413
|
+
const metaPath = path.join(sprintPath, 'meta.md');
|
|
414
|
+
const metaContent = fs.readFileSync(metaPath, 'utf-8');
|
|
415
|
+
|
|
416
|
+
// Task 목록/요약 파싱
|
|
417
|
+
const taskLines = metaContent
|
|
418
|
+
.split('\n')
|
|
419
|
+
.filter(line => line.trim().toLowerCase().startsWith('| task-'));
|
|
420
|
+
const knownStatuses = new Set(['BACKLOG', 'IN_DEV', 'IN_REVIEW', 'DONE', 'REJECT', 'REJECTED', 'BLOCKED']);
|
|
421
|
+
|
|
422
|
+
const completedTasks = [];
|
|
423
|
+
const incompleteTasks = [];
|
|
424
|
+
|
|
425
|
+
taskLines.forEach(line => {
|
|
426
|
+
const columns = line.split('|').map(col => col.trim()).filter(col => col.length > 0);
|
|
427
|
+
if (columns.length < 2) return;
|
|
428
|
+
|
|
429
|
+
const id = columns[0];
|
|
430
|
+
let status = null;
|
|
431
|
+
|
|
432
|
+
const candidate1 = normalizeTaskStatus(columns[1]);
|
|
433
|
+
if (knownStatuses.has(candidate1)) {
|
|
434
|
+
status = candidate1;
|
|
435
|
+
} else if (columns.length > 2) {
|
|
436
|
+
const candidate2 = normalizeTaskStatus(columns[2]);
|
|
437
|
+
if (knownStatuses.has(candidate2)) {
|
|
438
|
+
status = candidate2;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (status === 'DONE') {
|
|
443
|
+
completedTasks.push(id);
|
|
444
|
+
} else {
|
|
445
|
+
incompleteTasks.push(id);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
return { completedTasks, incompleteTasks };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* retrospective.md 파일 생성
|
|
454
|
+
*/
|
|
455
|
+
function createRetrospective(sprintPath, sprintName, endDate, data) {
|
|
456
|
+
const retrospectivePath = path.join(sprintPath, 'retrospective.md');
|
|
457
|
+
|
|
458
|
+
// 시작일은 meta.md에서 가져오기
|
|
459
|
+
const metaPath = path.join(sprintPath, 'meta.md');
|
|
460
|
+
const metaContent = fs.readFileSync(metaPath, 'utf-8');
|
|
461
|
+
const startDateMatch = metaContent.match(/시작일 \| ([\d-]+)/);
|
|
462
|
+
const startDate = startDateMatch ? startDateMatch[1] : endDate;
|
|
463
|
+
|
|
464
|
+
const sprintNumber = sprintName.replace('sprint-', '');
|
|
465
|
+
const totalTasks = data.completedTasks.length + data.incompleteTasks.length;
|
|
466
|
+
const completionRate = totalTasks > 0 ? Math.round((data.completedTasks.length / totalTasks) * 100) : 0;
|
|
467
|
+
|
|
468
|
+
const content = `# Sprint ${sprintNumber} 회고
|
|
469
|
+
|
|
470
|
+
## 기간
|
|
471
|
+
- 시작: ${startDate}
|
|
472
|
+
- 종료: ${endDate}
|
|
473
|
+
|
|
474
|
+
## 완료된 Task (${data.completedTasks.length}개)
|
|
475
|
+
|
|
476
|
+
${data.completedTasks.map(t => `- ${t}`).join('\n') || '- (없음)'}
|
|
477
|
+
|
|
478
|
+
## 미완료 Task (${data.incompleteTasks.length}개)
|
|
479
|
+
|
|
480
|
+
${data.incompleteTasks.map(t => `- ${t}: 이월 필요`).join('\n') || '- (없음)'}
|
|
481
|
+
|
|
482
|
+
## 잘된 점 (Keep)
|
|
483
|
+
|
|
484
|
+
- ${data.keep}
|
|
485
|
+
|
|
486
|
+
## 개선할 점 (Problem)
|
|
487
|
+
|
|
488
|
+
- ${data.problem}
|
|
489
|
+
|
|
490
|
+
## 시도할 것 (Try)
|
|
491
|
+
|
|
492
|
+
- ${data.try}
|
|
493
|
+
|
|
494
|
+
## 메트릭
|
|
495
|
+
|
|
496
|
+
- 계획 Task 수: ${totalTasks}개
|
|
497
|
+
- 완료 Task 수: ${data.completedTasks.length}개
|
|
498
|
+
- 완료율: ${completionRate}%
|
|
499
|
+
- 이월 Task 수: ${data.incompleteTasks.length}개
|
|
500
|
+
`;
|
|
501
|
+
|
|
502
|
+
fs.writeFileSync(retrospectivePath, content);
|
|
503
|
+
}
|
package/src/commands/status.js
CHANGED
|
@@ -4,7 +4,10 @@ import chalk from 'chalk';
|
|
|
4
4
|
import {
|
|
5
5
|
getWorkspaceDir,
|
|
6
6
|
getCurrentTemplate,
|
|
7
|
-
isWorkspaceSetup
|
|
7
|
+
isWorkspaceSetup,
|
|
8
|
+
getPackageVersion,
|
|
9
|
+
readVersion,
|
|
10
|
+
compareVersions
|
|
8
11
|
} from '../utils/files.js';
|
|
9
12
|
|
|
10
13
|
export async function status() {
|
|
@@ -30,6 +33,36 @@ export async function status() {
|
|
|
30
33
|
const artifactsDir = path.join(workspace, 'artifacts');
|
|
31
34
|
const rulesDir = path.join(workspace, 'rules');
|
|
32
35
|
|
|
36
|
+
// 버전 정보 확인
|
|
37
|
+
const packageVersion = getPackageVersion();
|
|
38
|
+
const versionInfo = readVersion();
|
|
39
|
+
const workspaceVersion = versionInfo ? (versionInfo.workspaceVersion || versionInfo.packageVersion) : null;
|
|
40
|
+
|
|
41
|
+
console.log(chalk.white.bold('버전:'));
|
|
42
|
+
console.log(chalk.gray(` 패키지: ${packageVersion}`));
|
|
43
|
+
if (workspaceVersion) {
|
|
44
|
+
console.log(chalk.gray(` 작업공간: ${workspaceVersion}`));
|
|
45
|
+
|
|
46
|
+
// 버전 비교
|
|
47
|
+
const versionDiff = compareVersions(packageVersion, workspaceVersion);
|
|
48
|
+
if (versionDiff > 0) {
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(chalk.yellow('⚠️ 새 버전이 있습니다!'));
|
|
51
|
+
console.log(chalk.gray(` 현재: ${workspaceVersion} → 최신: ${packageVersion}`));
|
|
52
|
+
console.log(chalk.gray(' 업그레이드하려면: ada upgrade'));
|
|
53
|
+
} else if (versionDiff < 0) {
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log(chalk.yellow('⚠️ 작업공간 버전이 패키지 버전보다 높습니다.'));
|
|
56
|
+
console.log(chalk.gray(' 개발 버전이거나 패키지 다운그레이드됨'));
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
console.log(chalk.gray(` 작업공간: 버전 정보 없음`));
|
|
60
|
+
console.log('');
|
|
61
|
+
console.log(chalk.yellow('⚠️ 이전 버전에서 생성된 작업공간입니다.'));
|
|
62
|
+
console.log(chalk.gray(' 업그레이드 권장: ada upgrade'));
|
|
63
|
+
}
|
|
64
|
+
console.log('');
|
|
65
|
+
|
|
33
66
|
// 템플릿 정보
|
|
34
67
|
console.log(chalk.white.bold('템플릿:'), chalk.green(template || '알 수 없음'));
|
|
35
68
|
console.log('');
|