aiwf 0.3.21 → 0.3.23

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 (140) hide show
  1. package/package.json +3 -2
  2. package/src/cli/index.js +39 -336
  3. package/src/lib/installer.js +310 -90
  4. package/src/utils/messages.js +24 -0
  5. package/src/DEPENDENCY_MAP.md +0 -90
  6. package/src/cli/cache-cli.js +0 -459
  7. package/src/cli/checkpoint-cli.js +0 -417
  8. package/src/cli/language-cli.js +0 -286
  9. package/src/cli/sprint-cli.js +0 -287
  10. package/src/commands/ai-tool.js +0 -383
  11. package/src/commands/compress.js +0 -60
  12. package/src/commands/create-project.js +0 -160
  13. package/src/commands/evaluate.js +0 -459
  14. package/src/commands/persona.js +0 -309
  15. package/src/commands/sprint-task.js +0 -262
  16. package/src/commands/token.js +0 -312
  17. package/src/lib/ai-persona-manager.js +0 -711
  18. package/src/lib/cache-system.js +0 -332
  19. package/src/lib/context-engine.js +0 -568
  20. package/src/lib/github-integration.js +0 -402
  21. package/src/lib/memory-profiler.js +0 -471
  22. package/src/lib/metrics-collector.js +0 -885
  23. package/src/lib/offline-detector.js +0 -386
  24. package/src/lib/resource-loader-enhanced.js +0 -399
  25. package/src/lib/resource-loader.js +0 -250
  26. package/src/lib/resources/commands/ai-persona.js +0 -602
  27. package/src/lib/resources/commands/compress-context.js +0 -389
  28. package/src/lib/resources/commands/evaluate.js +0 -246
  29. package/src/lib/resources/commands/persona-context-apply.js +0 -562
  30. package/src/lib/resources/commands/token-tracking.js +0 -443
  31. package/src/lib/resources/config/commit-patterns.js +0 -103
  32. package/src/lib/resources/config/language.json +0 -6
  33. package/src/lib/resources/personas/PERSONA_INDEX.md +0 -57
  34. package/src/lib/resources/personas/analyst.json +0 -44
  35. package/src/lib/resources/personas/architect/best_practices.md +0 -136
  36. package/src/lib/resources/personas/architect/knowledge_base.md +0 -377
  37. package/src/lib/resources/personas/architect.json +0 -44
  38. package/src/lib/resources/personas/backend/best_practices.md +0 -766
  39. package/src/lib/resources/personas/backend/knowledge_base.md +0 -1070
  40. package/src/lib/resources/personas/data_analyst/best_practices.md +0 -594
  41. package/src/lib/resources/personas/data_analyst/knowledge_base.md +0 -1057
  42. package/src/lib/resources/personas/developer.json +0 -44
  43. package/src/lib/resources/personas/developer.md +0 -37
  44. package/src/lib/resources/personas/evaluation_criteria.json +0 -137
  45. package/src/lib/resources/personas/frontend/best_practices.md +0 -445
  46. package/src/lib/resources/personas/frontend/knowledge_base.md +0 -729
  47. package/src/lib/resources/personas/persona-index.json +0 -31
  48. package/src/lib/resources/personas/reviewer.json +0 -44
  49. package/src/lib/resources/personas/security/best_practices.md +0 -307
  50. package/src/lib/resources/personas/security/knowledge_base.md +0 -498
  51. package/src/lib/resources/personas/tester.json +0 -44
  52. package/src/lib/resources/templates/README.md +0 -78
  53. package/src/lib/resources/templates/api-server/config.json +0 -64
  54. package/src/lib/resources/templates/api-server/template/.aiwf/config.json +0 -56
  55. package/src/lib/resources/templates/api-server/template/.aiwf/feature-ledger.json +0 -42
  56. package/src/lib/resources/templates/api-server/template/.aiwf/personas/backend-engineer.json +0 -40
  57. package/src/lib/resources/templates/api-server/template/.aiwf/scripts/cli.js +0 -111
  58. package/src/lib/resources/templates/api-server/template/.env.example +0 -29
  59. package/src/lib/resources/templates/api-server/template/.eslintrc.json +0 -23
  60. package/src/lib/resources/templates/api-server/template/README.md +0 -171
  61. package/src/lib/resources/templates/api-server/template/jest.config.js +0 -26
  62. package/src/lib/resources/templates/api-server/template/nodemon.json +0 -9
  63. package/src/lib/resources/templates/api-server/template/package.json +0 -61
  64. package/src/lib/resources/templates/api-server/template/src/app.ts +0 -60
  65. package/src/lib/resources/templates/api-server/template/src/config/swagger.ts +0 -38
  66. package/src/lib/resources/templates/api-server/template/src/controllers/aiwfController.ts +0 -54
  67. package/src/lib/resources/templates/api-server/template/src/controllers/authController.ts +0 -128
  68. package/src/lib/resources/templates/api-server/template/src/controllers/statusController.ts +0 -28
  69. package/src/lib/resources/templates/api-server/template/src/index.ts +0 -28
  70. package/src/lib/resources/templates/api-server/template/src/middleware/aiwfMiddleware.ts +0 -50
  71. package/src/lib/resources/templates/api-server/template/src/middleware/authMiddleware.ts +0 -41
  72. package/src/lib/resources/templates/api-server/template/src/middleware/errorHandler.ts +0 -42
  73. package/src/lib/resources/templates/api-server/template/src/middleware/notFoundHandler.ts +0 -14
  74. package/src/lib/resources/templates/api-server/template/src/middleware/requestLogger.ts +0 -20
  75. package/src/lib/resources/templates/api-server/template/src/routes/aiwf.ts +0 -31
  76. package/src/lib/resources/templates/api-server/template/src/routes/index.ts +0 -13
  77. package/src/lib/resources/templates/api-server/template/src/routes/v1/index.ts +0 -67
  78. package/src/lib/resources/templates/api-server/template/src/utils/logger.ts +0 -37
  79. package/src/lib/resources/templates/api-server/template/tests/app.test.ts +0 -40
  80. package/src/lib/resources/templates/api-server/template/tsconfig.json +0 -43
  81. package/src/lib/resources/templates/npm-library/config.json +0 -61
  82. package/src/lib/resources/templates/npm-library/template/LICENSE +0 -21
  83. package/src/lib/resources/templates/npm-library/template/README.md +0 -201
  84. package/src/lib/resources/templates/npm-library/template/package.json +0 -78
  85. package/src/lib/resources/templates/web-app/config.json +0 -53
  86. package/src/lib/resources/templates/web-app/template/.aiwf/config.json +0 -54
  87. package/src/lib/resources/templates/web-app/template/.aiwf/feature-ledger.json +0 -37
  88. package/src/lib/resources/templates/web-app/template/.aiwf/personas/fullstack-developer.json +0 -40
  89. package/src/lib/resources/templates/web-app/template/.aiwf/scripts/cli.js +0 -110
  90. package/src/lib/resources/templates/web-app/template/.eslintrc.cjs +0 -20
  91. package/src/lib/resources/templates/web-app/template/README.md +0 -151
  92. package/src/lib/resources/templates/web-app/template/index.html +0 -14
  93. package/src/lib/resources/templates/web-app/template/package.json +0 -44
  94. package/src/lib/resources/templates/web-app/template/postcss.config.js +0 -6
  95. package/src/lib/resources/templates/web-app/template/public/vite.svg +0 -1
  96. package/src/lib/resources/templates/web-app/template/src/App.tsx +0 -21
  97. package/src/lib/resources/templates/web-app/template/src/components/Layout.tsx +0 -57
  98. package/src/lib/resources/templates/web-app/template/src/components/aiwf/ContextStatus.tsx +0 -107
  99. package/src/lib/resources/templates/web-app/template/src/components/aiwf/TokenUsage.tsx +0 -71
  100. package/src/lib/resources/templates/web-app/template/src/index.css +0 -60
  101. package/src/lib/resources/templates/web-app/template/src/main.tsx +0 -10
  102. package/src/lib/resources/templates/web-app/template/src/pages/AiwfDashboard.tsx +0 -57
  103. package/src/lib/resources/templates/web-app/template/src/pages/HomePage.tsx +0 -89
  104. package/src/lib/resources/templates/web-app/template/src/pages/NotFound.tsx +0 -25
  105. package/src/lib/resources/templates/web-app/template/src/stores/aiwfStore.ts +0 -126
  106. package/src/lib/resources/templates/web-app/template/src/types/global.d.ts +0 -9
  107. package/src/lib/resources/templates/web-app/template/src/vite-env.d.ts +0 -1
  108. package/src/lib/resources/templates/web-app/template/tailwind.config.js +0 -30
  109. package/src/lib/resources/templates/web-app/template/tsconfig.json +0 -37
  110. package/src/lib/resources/templates/web-app/template/tsconfig.node.json +0 -10
  111. package/src/lib/resources/templates/web-app/template/vite.config.ts +0 -23
  112. package/src/lib/resources/utils/background-monitor.js +0 -223
  113. package/src/lib/resources/utils/compression-metrics.js +0 -1031
  114. package/src/lib/resources/utils/compression-strategies.js +0 -557
  115. package/src/lib/resources/utils/content-normalizer.js +0 -455
  116. package/src/lib/resources/utils/context-compressor.js +0 -583
  117. package/src/lib/resources/utils/context-rule-parser.js +0 -180
  118. package/src/lib/resources/utils/context-token-monitor.js +0 -463
  119. package/src/lib/resources/utils/context-update-manager.js +0 -502
  120. package/src/lib/resources/utils/git-utils.js +0 -220
  121. package/src/lib/resources/utils/importance-classifier.js +0 -645
  122. package/src/lib/resources/utils/information-filter.js +0 -710
  123. package/src/lib/resources/utils/persona-aware-compressor.js +0 -598
  124. package/src/lib/resources/utils/prompt-injector.js +0 -242
  125. package/src/lib/resources/utils/simplified-evaluator.js +0 -119
  126. package/src/lib/resources/utils/text-summarizer.js +0 -492
  127. package/src/lib/resources/utils/token-counter.js +0 -177
  128. package/src/lib/resources/utils/token-monitor.js +0 -484
  129. package/src/lib/resources/utils/token-reporter.js +0 -452
  130. package/src/lib/resources/utils/token-storage.js +0 -433
  131. package/src/lib/resources/utils/token-tracker.js +0 -283
  132. package/src/lib/state/priority-calculator.js +0 -217
  133. package/src/lib/state/state-index.js +0 -155
  134. package/src/lib/state/task-scanner.js +0 -328
  135. package/src/lib/task-analyzer.js +0 -599
  136. package/src/lib/template-cache-system.js +0 -559
  137. package/src/lib/template-downloader.js +0 -467
  138. package/src/lib/template-version-manager.js +0 -491
  139. package/src/lib/token-optimizer.js +0 -517
  140. package/src/utils/engineering-guard.js +0 -399
@@ -1,217 +0,0 @@
1
- /**
2
- * AIWF Priority Calculator
3
- * 태스크 우선순위 계산 및 관리
4
- */
5
-
6
- export class PriorityCalculator {
7
- constructor(workflowRules) {
8
- this.weights = workflowRules?.priority_weights || {
9
- urgency: 0.4,
10
- importance: 0.3,
11
- dependencies: 0.2,
12
- effort: 0.1
13
- };
14
- }
15
-
16
- /**
17
- * 태스크의 긴급도 계산
18
- * - 마감일까지 남은 시간
19
- * - 차단된 다른 태스크 수
20
- * - 마일스톤 내 위치
21
- */
22
- calculateUrgency(task, stateIndex) {
23
- let urgencyScore = 0;
24
-
25
- // 마감일 기반 점수 (0-1)
26
- if (task.due_date) {
27
- const dueDate = new Date(task.due_date);
28
- const now = new Date();
29
- const daysUntilDue = Math.ceil((dueDate - now) / (1000 * 60 * 60 * 24));
30
-
31
- if (daysUntilDue <= 0) urgencyScore += 1.0; // 이미 지남
32
- else if (daysUntilDue <= 1) urgencyScore += 0.9; // 1일 이내
33
- else if (daysUntilDue <= 3) urgencyScore += 0.7; // 3일 이내
34
- else if (daysUntilDue <= 7) urgencyScore += 0.5; // 1주 이내
35
- else urgencyScore += Math.max(0, 1 - daysUntilDue / 30); // 선형 감소
36
- }
37
-
38
- // 차단된 태스크 수 기반 점수
39
- const blockedByThis = this.countTasksBlockedBy(task.id, stateIndex);
40
- urgencyScore += Math.min(blockedByThis * 0.2, 0.5);
41
-
42
- // 스프린트 진행도 기반 점수
43
- if (task.sprint_id && stateIndex.sprint_summary[task.sprint_id]) {
44
- const sprintProgress = stateIndex.sprint_summary[task.sprint_id].progress || 0;
45
- if (sprintProgress > 0.8) urgencyScore += 0.3; // 스프린트 거의 완료
46
- else if (sprintProgress > 0.5) urgencyScore += 0.1;
47
- }
48
-
49
- return Math.min(urgencyScore, 1.0);
50
- }
51
-
52
- /**
53
- * 태스크의 중요도 계산
54
- * - 비즈니스 가치
55
- * - 아키텍처적 영향
56
- * - 프로젝트 목표와의 연관성
57
- */
58
- calculateImportance(task, stateIndex) {
59
- let importanceScore = 0;
60
-
61
- // 태스크 타입별 기본 중요도
62
- const typeWeights = {
63
- 'architecture': 0.9,
64
- 'security': 0.8,
65
- 'core-feature': 0.7,
66
- 'bug-fix': 0.6,
67
- 'enhancement': 0.5,
68
- 'documentation': 0.3,
69
- 'refactoring': 0.4,
70
- 'testing': 0.5
71
- };
72
-
73
- importanceScore += typeWeights[task.type] || 0.5;
74
-
75
- // 마일스톤 중요도 반영
76
- if (task.milestone_id) {
77
- const milestoneId = task.milestone_id;
78
- // M01, M02 등에서 숫자 추출하여 초기 마일스톤일수록 높은 점수
79
- const milestoneNum = parseInt(milestoneId.replace(/\D/g, ''));
80
- if (milestoneNum <= 2) importanceScore += 0.2;
81
- else if (milestoneNum <= 5) importanceScore += 0.1;
82
- }
83
-
84
- // 태그 기반 중요도 조정
85
- if (task.tags) {
86
- if (task.tags.includes('critical')) importanceScore += 0.3;
87
- if (task.tags.includes('high-priority')) importanceScore += 0.2;
88
- if (task.tags.includes('low-priority')) importanceScore -= 0.2;
89
- }
90
-
91
- return Math.min(importanceScore, 1.0);
92
- }
93
-
94
- /**
95
- * 종속성 점수 계산
96
- * - 이 태스크에 의존하는 태스크 수
97
- * - 이 태스크가 의존하는 태스크의 완료 상태
98
- */
99
- calculateDependencyScore(task, stateIndex) {
100
- let dependencyScore = 0;
101
-
102
- // 이 태스크를 차단하는 미완료 종속성
103
- const blockingDependencies = task.dependencies?.filter(depId => {
104
- const depTask = stateIndex.tasks[depId];
105
- return depTask && depTask.status !== 'completed';
106
- }) || [];
107
-
108
- if (blockingDependencies.length === 0) {
109
- dependencyScore += 0.5; // 차단 요소 없음
110
- } else {
111
- dependencyScore -= blockingDependencies.length * 0.2; // 차단 요소마다 감점
112
- }
113
-
114
- // 이 태스크에 의존하는 태스크 수
115
- const dependentCount = this.countTasksBlockedBy(task.id, stateIndex);
116
- dependencyScore += Math.min(dependentCount * 0.15, 0.5);
117
-
118
- return Math.max(0, Math.min(dependencyScore, 1.0));
119
- }
120
-
121
- /**
122
- * 노력 점수 계산 (낮을수록 높은 점수)
123
- * - 예상 작업 시간
124
- * - 복잡도
125
- * - 필요한 리소스
126
- */
127
- calculateEffortScore(task) {
128
- let effortScore = 1.0; // 기본값: 쉬운 태스크
129
-
130
- // 예상 시간 기반 점수
131
- if (task.estimated_hours) {
132
- if (task.estimated_hours <= 1) effortScore = 1.0; // 1시간 이하
133
- else if (task.estimated_hours <= 4) effortScore = 0.8; // 4시간 이하
134
- else if (task.estimated_hours <= 8) effortScore = 0.6; // 1일 이하
135
- else if (task.estimated_hours <= 16) effortScore = 0.4; // 2일 이하
136
- else effortScore = 0.2; // 2일 초과
137
- }
138
-
139
- // 복잡도 기반 조정
140
- if (task.complexity) {
141
- const complexityWeights = {
142
- 'low': 1.0,
143
- 'medium': 0.7,
144
- 'high': 0.4,
145
- 'very-high': 0.2
146
- };
147
- effortScore *= complexityWeights[task.complexity] || 0.7;
148
- }
149
-
150
- // 태그 기반 조정 (간단한 태스크들)
151
- if (task.tags) {
152
- if (task.tags.includes('quick-fix')) effortScore += 0.2;
153
- if (task.tags.includes('research-heavy')) effortScore -= 0.3;
154
- }
155
-
156
- return Math.max(0.1, Math.min(effortScore, 1.0));
157
- }
158
-
159
- /**
160
- * 전체 우선순위 점수 계산
161
- */
162
- calculatePriority(task, stateIndex) {
163
- const urgency = this.calculateUrgency(task, stateIndex);
164
- const importance = this.calculateImportance(task, stateIndex);
165
- const dependency = this.calculateDependencyScore(task, stateIndex);
166
- const effort = this.calculateEffortScore(task);
167
-
168
- const priority = (
169
- urgency * this.weights.urgency +
170
- importance * this.weights.importance +
171
- dependency * this.weights.dependencies +
172
- effort * this.weights.effort
173
- );
174
-
175
- return {
176
- total: Math.round(priority * 100) / 100,
177
- breakdown: {
178
- urgency: Math.round(urgency * 100) / 100,
179
- importance: Math.round(importance * 100) / 100,
180
- dependency: Math.round(dependency * 100) / 100,
181
- effort: Math.round(effort * 100) / 100
182
- }
183
- };
184
- }
185
-
186
- /**
187
- * 특정 태스크에 의해 차단된 태스크 수 계산
188
- */
189
- countTasksBlockedBy(taskId, stateIndex) {
190
- let count = 0;
191
- for (const [, task] of Object.entries(stateIndex.tasks)) {
192
- if (task.dependencies?.includes(taskId) && task.status !== 'completed') {
193
- count++;
194
- }
195
- }
196
- return count;
197
- }
198
-
199
- /**
200
- * 모든 태스크의 우선순위 재계산
201
- */
202
- recalculateAllPriorities(stateIndex) {
203
- const updatedTasks = {};
204
-
205
- for (const [taskId, task] of Object.entries(stateIndex.tasks)) {
206
- const priorityData = this.calculatePriority(task, stateIndex);
207
- updatedTasks[taskId] = {
208
- ...task,
209
- priority_score: priorityData.total,
210
- priority_breakdown: priorityData.breakdown,
211
- last_priority_update: new Date().toISOString()
212
- };
213
- }
214
-
215
- return updatedTasks;
216
- }
217
- }
@@ -1,155 +0,0 @@
1
- /**
2
- * AIWF State Index Manager
3
- * 중앙 상태 인덱스 파일의 생성, 로드, 저장을 담당
4
- */
5
-
6
- import fs from 'fs/promises';
7
- import path from 'path';
8
- import { existsSync } from 'fs';
9
-
10
- export class StateIndexManager {
11
- constructor(aiwfPath) {
12
- this.aiwfPath = aiwfPath;
13
- this.stateFile = 'task-state-index.json';
14
- this.workflowRulesFile = 'workflow-rules.json';
15
- }
16
-
17
- get indexPath() {
18
- return path.join(this.aiwfPath, this.stateFile);
19
- }
20
-
21
- get workflowRulesPath() {
22
- return path.join(this.aiwfPath, this.workflowRulesFile);
23
- }
24
-
25
- /**
26
- * 파일 존재 여부 확인
27
- */
28
- async fileExists(filePath) {
29
- try {
30
- await fs.access(filePath);
31
- return true;
32
- } catch {
33
- return false;
34
- }
35
- }
36
-
37
- /**
38
- * 초기 상태 구조 생성
39
- */
40
- createInitialState(projectRoot) {
41
- return {
42
- version: '1.0.0',
43
- last_updated: new Date().toISOString(),
44
- last_updated_by: 'aiwf-state-init',
45
- project_info: {
46
- name: path.basename(projectRoot),
47
- current_milestone: null,
48
- current_sprint: null,
49
- phase: 'planning'
50
- },
51
- current_focus: {
52
- primary_task: null,
53
- secondary_tasks: [],
54
- blocked_tasks: [],
55
- last_worked_on: null,
56
- session_notes: ''
57
- },
58
- tasks: {},
59
- sprint_summary: {},
60
- next_actions: [],
61
- ai_context: {
62
- last_session: new Date().toISOString(),
63
- session_count: 0,
64
- learned_patterns: {},
65
- working_hours: {}
66
- },
67
- metrics: {
68
- total_tasks: 0,
69
- completed_tasks: 0,
70
- in_progress_tasks: 0,
71
- pending_tasks: 0,
72
- blocked_tasks: 0,
73
- overall_progress: 0
74
- }
75
- };
76
- }
77
-
78
- /**
79
- * 상태 인덱스 초기화
80
- */
81
- async initializeIndex(projectRoot) {
82
- const exists = await this.fileExists(this.indexPath);
83
-
84
- if (exists) {
85
- return { success: false, message: 'State index already exists' };
86
- }
87
-
88
- const initialState = this.createInitialState(projectRoot);
89
- await fs.writeFile(this.indexPath, JSON.stringify(initialState, null, 2));
90
-
91
- return { success: true, message: 'State index initialized successfully' };
92
- }
93
-
94
- /**
95
- * 상태 인덱스 로드
96
- */
97
- async loadIndex() {
98
- if (!existsSync(this.indexPath)) {
99
- return null;
100
- }
101
-
102
- try {
103
- const content = await fs.readFile(this.indexPath, 'utf8');
104
- return JSON.parse(content);
105
- } catch (error) {
106
- throw new Error(`Failed to load state index: ${error.message}`);
107
- }
108
- }
109
-
110
- /**
111
- * 상태 인덱스 저장
112
- */
113
- async saveIndex(stateIndex) {
114
- stateIndex.last_updated = new Date().toISOString();
115
- stateIndex.last_updated_by = 'aiwf-state-update';
116
-
117
- await fs.writeFile(this.indexPath, JSON.stringify(stateIndex, null, 2));
118
- }
119
-
120
- /**
121
- * 워크플로우 규칙 로드
122
- */
123
- async loadWorkflowRules() {
124
- if (!existsSync(this.workflowRulesPath)) {
125
- // 기본 워크플로우 규칙 생성
126
- const defaultRules = {
127
- priority_weights: {
128
- urgency: 0.4,
129
- importance: 0.3,
130
- dependencies: 0.2,
131
- effort: 0.1
132
- },
133
- completion_threshold: 0.8,
134
- max_active_tasks: 3,
135
- dependency_timeout_days: 7,
136
- status_transitions: {
137
- 'pending': ['in_progress', 'blocked'],
138
- 'in_progress': ['completed', 'blocked', 'pending'],
139
- 'blocked': ['in_progress', 'pending'],
140
- 'completed': []
141
- }
142
- };
143
-
144
- await fs.writeFile(this.workflowRulesPath, JSON.stringify(defaultRules, null, 2));
145
- return defaultRules;
146
- }
147
-
148
- try {
149
- const content = await fs.readFile(this.workflowRulesPath, 'utf8');
150
- return JSON.parse(content);
151
- } catch (error) {
152
- throw new Error(`Failed to load workflow rules: ${error.message}`);
153
- }
154
- }
155
- }
@@ -1,328 +0,0 @@
1
- /**
2
- * AIWF Task Scanner
3
- * 프로젝트 디렉토리에서 태스크 파일들을 스캔하고 파싱
4
- */
5
-
6
- import fs from 'fs/promises';
7
- import path from 'path';
8
- import { glob } from 'glob';
9
-
10
- export class TaskScanner {
11
- constructor(aiwfPath) {
12
- this.aiwfPath = aiwfPath;
13
- }
14
-
15
- /**
16
- * 스프린트 디렉토리 스캔
17
- */
18
- async scanSprintDirectories() {
19
- try {
20
- const sprintDirs = await glob('03_SPRINTS/S*/', { cwd: this.aiwfPath });
21
- const sprintData = {};
22
-
23
- for (const sprintDir of sprintDirs) {
24
- const sprintId = path.basename(sprintDir);
25
- const fullSprintPath = path.join(this.aiwfPath, sprintDir);
26
-
27
- // 스프린트 메타 파일 찾기
28
- const metaFiles = await glob('*_meta.md', { cwd: fullSprintPath });
29
- let sprintMeta = {};
30
-
31
- if (metaFiles.length > 0) {
32
- const metaPath = path.join(fullSprintPath, metaFiles[0]);
33
- sprintMeta = await this.parseSprintMeta(metaPath);
34
- }
35
-
36
- // 태스크 파일들 스캔
37
- const taskFiles = await glob('T*.md', { cwd: fullSprintPath });
38
- const tasks = {};
39
-
40
- for (const taskFile of taskFiles) {
41
- const taskPath = path.join(fullSprintPath, taskFile);
42
- const taskData = await this.parseTaskFile(taskPath, sprintId);
43
- if (taskData) {
44
- tasks[taskData.id] = taskData;
45
- }
46
- }
47
-
48
- sprintData[sprintId] = {
49
- meta: sprintMeta,
50
- tasks: tasks,
51
- task_count: taskFiles.length,
52
- path: sprintDir
53
- };
54
- }
55
-
56
- return sprintData;
57
- } catch (error) {
58
- throw new Error(`Failed to scan sprint directories: ${error.message}`);
59
- }
60
- }
61
-
62
- /**
63
- * 일반 태스크 디렉토리 스캔
64
- */
65
- async scanGeneralTasks() {
66
- try {
67
- const generalTasksPath = path.join(this.aiwfPath, '04_GENERAL_TASKS');
68
- const taskFiles = await glob('*.md', { cwd: generalTasksPath });
69
- const tasks = {};
70
-
71
- for (const taskFile of taskFiles) {
72
- const taskPath = path.join(generalTasksPath, taskFile);
73
- const taskData = await this.parseTaskFile(taskPath, 'general');
74
- if (taskData) {
75
- tasks[taskData.id] = taskData;
76
- }
77
- }
78
-
79
- return tasks;
80
- } catch (error) {
81
- // 일반 태스크 디렉토리가 없으면 빈 객체 반환
82
- return {};
83
- }
84
- }
85
-
86
- /**
87
- * 스프린트 메타 파일 파싱
88
- */
89
- async parseSprintMeta(filePath) {
90
- try {
91
- const content = await fs.readFile(filePath, 'utf8');
92
- const meta = {
93
- status: 'planned',
94
- progress: 0,
95
- start_date: null,
96
- end_date: null,
97
- goals: [],
98
- deliverables: []
99
- };
100
-
101
- // YAML frontmatter 파싱
102
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
103
- if (frontmatterMatch) {
104
- const frontmatter = frontmatterMatch[1];
105
-
106
- // 간단한 YAML 파싱 (key: value 형태)
107
- const lines = frontmatter.split('\n');
108
- for (const line of lines) {
109
- const [key, ...valueParts] = line.split(':');
110
- if (key && valueParts.length > 0) {
111
- const value = valueParts.join(':').trim();
112
- meta[key.trim()] = value;
113
- }
114
- }
115
- }
116
-
117
- // 본문에서 목표와 결과물 추출
118
- const bodyContent = content.replace(/^---\n[\s\S]*?\n---\n/, '');
119
-
120
- // 목표 섹션 찾기
121
- const goalsMatch = bodyContent.match(/## 목표[^#]*?((?:- .*\n?)*)/i);
122
- if (goalsMatch) {
123
- meta.goals = goalsMatch[1]
124
- .split('\n')
125
- .filter(line => line.trim().startsWith('-'))
126
- .map(line => line.replace(/^-\s*/, '').trim());
127
- }
128
-
129
- // 결과물 섹션 찾기
130
- const deliverablesMatch = bodyContent.match(/## 결과물[^#]*?((?:- .*\n?)*)/i);
131
- if (deliverablesMatch) {
132
- meta.deliverables = deliverablesMatch[1]
133
- .split('\n')
134
- .filter(line => line.trim().startsWith('-'))
135
- .map(line => line.replace(/^-\s*/, '').trim());
136
- }
137
-
138
- return meta;
139
- } catch (error) {
140
- console.warn(`Failed to parse sprint meta ${filePath}: ${error.message}`);
141
- return {};
142
- }
143
- }
144
-
145
- /**
146
- * 태스크 파일 파싱
147
- */
148
- async parseTaskFile(filePath, sprintId) {
149
- try {
150
- const content = await fs.readFile(filePath, 'utf8');
151
- const fileName = path.basename(filePath, '.md');
152
-
153
- const task = {
154
- id: fileName,
155
- sprint_id: sprintId,
156
- title: this.extractTitle(content),
157
- status: this.determineStatus(content),
158
- description: this.extractDescription(content),
159
- tags: this.extractTags(content),
160
- dependencies: this.extractDependencies(content),
161
- assignee: this.extractAssignee(content),
162
- created_date: this.extractCreatedDate(filePath),
163
- updated_date: this.extractUpdatedDate(filePath),
164
- estimated_hours: this.extractEstimatedHours(content),
165
- complexity: this.extractComplexity(content),
166
- type: this.extractTaskType(content),
167
- milestone_id: this.extractMilestoneId(sprintId),
168
- file_path: filePath,
169
- progress_notes: this.extractProgressNotes(content)
170
- };
171
-
172
- return task;
173
- } catch (error) {
174
- console.warn(`Failed to parse task file ${filePath}: ${error.message}`);
175
- return null;
176
- }
177
- }
178
-
179
- /**
180
- * 태스크 제목 추출
181
- */
182
- extractTitle(content) {
183
- const titleMatch = content.match(/^#\s+(.+)$/m);
184
- return titleMatch ? titleMatch[1].trim() : 'Untitled Task';
185
- }
186
-
187
- /**
188
- * 태스크 상태 결정
189
- */
190
- determineStatus(content) {
191
- // OUTPUT LOG에서 완료 표시 확인
192
- if (content.includes('## OUTPUT LOG') || content.includes('✅ 완료')) {
193
- const outputSection = content.match(/## OUTPUT LOG([\s\S]*?)(?=##|$)/i);
194
- if (outputSection && outputSection[1].trim().length > 50) {
195
- return 'completed';
196
- }
197
- }
198
-
199
- // 진행 중 표시 확인
200
- if (content.includes('🔄') || content.includes('진행 중') || content.includes('in progress')) {
201
- return 'in_progress';
202
- }
203
-
204
- // 차단됨 표시 확인
205
- if (content.includes('❌') || content.includes('차단') || content.includes('blocked')) {
206
- return 'blocked';
207
- }
208
-
209
- return 'pending';
210
- }
211
-
212
- /**
213
- * 태스크 설명 추출
214
- */
215
- extractDescription(content) {
216
- // 첫 번째 헤더 다음부터 두 번째 헤더 전까지
217
- const descMatch = content.match(/^#[^#\n]*\n([\s\S]*?)(?=\n##|\n#|$)/);
218
- return descMatch ? descMatch[1].trim().substring(0, 200) : '';
219
- }
220
-
221
- /**
222
- * 태그 추출
223
- */
224
- extractTags(content) {
225
- const tagMatches = content.match(/#(\w+)/g);
226
- return tagMatches ? tagMatches.map(tag => tag.substring(1)) : [];
227
- }
228
-
229
- /**
230
- * 종속성 추출
231
- */
232
- extractDependencies(content) {
233
- const depMatches = content.match(/depends on:?\s*([T\d\s,]+)/i);
234
- if (depMatches) {
235
- return depMatches[1]
236
- .split(/[,\s]+/)
237
- .filter(dep => dep.match(/^T\d+$/))
238
- .map(dep => dep.trim());
239
- }
240
- return [];
241
- }
242
-
243
- /**
244
- * 담당자 추출
245
- */
246
- extractAssignee(content) {
247
- const assigneeMatch = content.match(/assignee:?\s*([^\n]+)/i);
248
- return assigneeMatch ? assigneeMatch[1].trim() : null;
249
- }
250
-
251
- /**
252
- * 생성일 추출 (파일 생성 시간 기반)
253
- */
254
- async extractCreatedDate(filePath) {
255
- try {
256
- const stats = await fs.stat(filePath);
257
- return stats.birthtime.toISOString();
258
- } catch {
259
- return new Date().toISOString();
260
- }
261
- }
262
-
263
- /**
264
- * 수정일 추출 (파일 수정 시간 기반)
265
- */
266
- async extractUpdatedDate(filePath) {
267
- try {
268
- const stats = await fs.stat(filePath);
269
- return stats.mtime.toISOString();
270
- } catch {
271
- return new Date().toISOString();
272
- }
273
- }
274
-
275
- /**
276
- * 예상 시간 추출
277
- */
278
- extractEstimatedHours(content) {
279
- const hourMatch = content.match(/estimated:?\s*(\d+\.?\d*)\s*hours?/i);
280
- return hourMatch ? parseFloat(hourMatch[1]) : null;
281
- }
282
-
283
- /**
284
- * 복잡도 추출
285
- */
286
- extractComplexity(content) {
287
- const complexityMatch = content.match(/complexity:?\s*(low|medium|high|very-high)/i);
288
- return complexityMatch ? complexityMatch[1].toLowerCase() : null;
289
- }
290
-
291
- /**
292
- * 태스크 타입 추출
293
- */
294
- extractTaskType(content) {
295
- const typeMatch = content.match(/type:?\s*([^\n]+)/i);
296
- if (typeMatch) {
297
- return typeMatch[1].trim().toLowerCase();
298
- }
299
-
300
- // 제목이나 내용에서 타입 추정
301
- const title = this.extractTitle(content).toLowerCase();
302
- if (title.includes('bug') || title.includes('fix')) return 'bug-fix';
303
- if (title.includes('test')) return 'testing';
304
- if (title.includes('doc')) return 'documentation';
305
- if (title.includes('refactor')) return 'refactoring';
306
- if (title.includes('feature')) return 'core-feature';
307
-
308
- return 'enhancement';
309
- }
310
-
311
- /**
312
- * 마일스톤 ID 추출 (스프린트 ID에서)
313
- */
314
- extractMilestoneId(sprintId) {
315
- if (sprintId === 'general') return null;
316
-
317
- const milestoneMatch = sprintId.match(/S\d+_M(\d+)/);
318
- return milestoneMatch ? `M${milestoneMatch[1].padStart(2, '0')}` : null;
319
- }
320
-
321
- /**
322
- * 진행 노트 추출
323
- */
324
- extractProgressNotes(content) {
325
- const notesMatch = content.match(/## 진행 상황([\s\S]*?)(?=##|$)/i);
326
- return notesMatch ? notesMatch[1].trim() : '';
327
- }
328
- }