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.
- package/package.json +3 -2
- package/src/cli/index.js +39 -336
- package/src/lib/installer.js +310 -90
- package/src/utils/messages.js +24 -0
- package/src/DEPENDENCY_MAP.md +0 -90
- package/src/cli/cache-cli.js +0 -459
- package/src/cli/checkpoint-cli.js +0 -417
- package/src/cli/language-cli.js +0 -286
- package/src/cli/sprint-cli.js +0 -287
- package/src/commands/ai-tool.js +0 -383
- package/src/commands/compress.js +0 -60
- package/src/commands/create-project.js +0 -160
- package/src/commands/evaluate.js +0 -459
- package/src/commands/persona.js +0 -309
- package/src/commands/sprint-task.js +0 -262
- package/src/commands/token.js +0 -312
- package/src/lib/ai-persona-manager.js +0 -711
- package/src/lib/cache-system.js +0 -332
- package/src/lib/context-engine.js +0 -568
- package/src/lib/github-integration.js +0 -402
- package/src/lib/memory-profiler.js +0 -471
- package/src/lib/metrics-collector.js +0 -885
- package/src/lib/offline-detector.js +0 -386
- package/src/lib/resource-loader-enhanced.js +0 -399
- package/src/lib/resource-loader.js +0 -250
- package/src/lib/resources/commands/ai-persona.js +0 -602
- package/src/lib/resources/commands/compress-context.js +0 -389
- package/src/lib/resources/commands/evaluate.js +0 -246
- package/src/lib/resources/commands/persona-context-apply.js +0 -562
- package/src/lib/resources/commands/token-tracking.js +0 -443
- package/src/lib/resources/config/commit-patterns.js +0 -103
- package/src/lib/resources/config/language.json +0 -6
- package/src/lib/resources/personas/PERSONA_INDEX.md +0 -57
- package/src/lib/resources/personas/analyst.json +0 -44
- package/src/lib/resources/personas/architect/best_practices.md +0 -136
- package/src/lib/resources/personas/architect/knowledge_base.md +0 -377
- package/src/lib/resources/personas/architect.json +0 -44
- package/src/lib/resources/personas/backend/best_practices.md +0 -766
- package/src/lib/resources/personas/backend/knowledge_base.md +0 -1070
- package/src/lib/resources/personas/data_analyst/best_practices.md +0 -594
- package/src/lib/resources/personas/data_analyst/knowledge_base.md +0 -1057
- package/src/lib/resources/personas/developer.json +0 -44
- package/src/lib/resources/personas/developer.md +0 -37
- package/src/lib/resources/personas/evaluation_criteria.json +0 -137
- package/src/lib/resources/personas/frontend/best_practices.md +0 -445
- package/src/lib/resources/personas/frontend/knowledge_base.md +0 -729
- package/src/lib/resources/personas/persona-index.json +0 -31
- package/src/lib/resources/personas/reviewer.json +0 -44
- package/src/lib/resources/personas/security/best_practices.md +0 -307
- package/src/lib/resources/personas/security/knowledge_base.md +0 -498
- package/src/lib/resources/personas/tester.json +0 -44
- package/src/lib/resources/templates/README.md +0 -78
- package/src/lib/resources/templates/api-server/config.json +0 -64
- package/src/lib/resources/templates/api-server/template/.aiwf/config.json +0 -56
- package/src/lib/resources/templates/api-server/template/.aiwf/feature-ledger.json +0 -42
- package/src/lib/resources/templates/api-server/template/.aiwf/personas/backend-engineer.json +0 -40
- package/src/lib/resources/templates/api-server/template/.aiwf/scripts/cli.js +0 -111
- package/src/lib/resources/templates/api-server/template/.env.example +0 -29
- package/src/lib/resources/templates/api-server/template/.eslintrc.json +0 -23
- package/src/lib/resources/templates/api-server/template/README.md +0 -171
- package/src/lib/resources/templates/api-server/template/jest.config.js +0 -26
- package/src/lib/resources/templates/api-server/template/nodemon.json +0 -9
- package/src/lib/resources/templates/api-server/template/package.json +0 -61
- package/src/lib/resources/templates/api-server/template/src/app.ts +0 -60
- package/src/lib/resources/templates/api-server/template/src/config/swagger.ts +0 -38
- package/src/lib/resources/templates/api-server/template/src/controllers/aiwfController.ts +0 -54
- package/src/lib/resources/templates/api-server/template/src/controllers/authController.ts +0 -128
- package/src/lib/resources/templates/api-server/template/src/controllers/statusController.ts +0 -28
- package/src/lib/resources/templates/api-server/template/src/index.ts +0 -28
- package/src/lib/resources/templates/api-server/template/src/middleware/aiwfMiddleware.ts +0 -50
- package/src/lib/resources/templates/api-server/template/src/middleware/authMiddleware.ts +0 -41
- package/src/lib/resources/templates/api-server/template/src/middleware/errorHandler.ts +0 -42
- package/src/lib/resources/templates/api-server/template/src/middleware/notFoundHandler.ts +0 -14
- package/src/lib/resources/templates/api-server/template/src/middleware/requestLogger.ts +0 -20
- package/src/lib/resources/templates/api-server/template/src/routes/aiwf.ts +0 -31
- package/src/lib/resources/templates/api-server/template/src/routes/index.ts +0 -13
- package/src/lib/resources/templates/api-server/template/src/routes/v1/index.ts +0 -67
- package/src/lib/resources/templates/api-server/template/src/utils/logger.ts +0 -37
- package/src/lib/resources/templates/api-server/template/tests/app.test.ts +0 -40
- package/src/lib/resources/templates/api-server/template/tsconfig.json +0 -43
- package/src/lib/resources/templates/npm-library/config.json +0 -61
- package/src/lib/resources/templates/npm-library/template/LICENSE +0 -21
- package/src/lib/resources/templates/npm-library/template/README.md +0 -201
- package/src/lib/resources/templates/npm-library/template/package.json +0 -78
- package/src/lib/resources/templates/web-app/config.json +0 -53
- package/src/lib/resources/templates/web-app/template/.aiwf/config.json +0 -54
- package/src/lib/resources/templates/web-app/template/.aiwf/feature-ledger.json +0 -37
- package/src/lib/resources/templates/web-app/template/.aiwf/personas/fullstack-developer.json +0 -40
- package/src/lib/resources/templates/web-app/template/.aiwf/scripts/cli.js +0 -110
- package/src/lib/resources/templates/web-app/template/.eslintrc.cjs +0 -20
- package/src/lib/resources/templates/web-app/template/README.md +0 -151
- package/src/lib/resources/templates/web-app/template/index.html +0 -14
- package/src/lib/resources/templates/web-app/template/package.json +0 -44
- package/src/lib/resources/templates/web-app/template/postcss.config.js +0 -6
- package/src/lib/resources/templates/web-app/template/public/vite.svg +0 -1
- package/src/lib/resources/templates/web-app/template/src/App.tsx +0 -21
- package/src/lib/resources/templates/web-app/template/src/components/Layout.tsx +0 -57
- package/src/lib/resources/templates/web-app/template/src/components/aiwf/ContextStatus.tsx +0 -107
- package/src/lib/resources/templates/web-app/template/src/components/aiwf/TokenUsage.tsx +0 -71
- package/src/lib/resources/templates/web-app/template/src/index.css +0 -60
- package/src/lib/resources/templates/web-app/template/src/main.tsx +0 -10
- package/src/lib/resources/templates/web-app/template/src/pages/AiwfDashboard.tsx +0 -57
- package/src/lib/resources/templates/web-app/template/src/pages/HomePage.tsx +0 -89
- package/src/lib/resources/templates/web-app/template/src/pages/NotFound.tsx +0 -25
- package/src/lib/resources/templates/web-app/template/src/stores/aiwfStore.ts +0 -126
- package/src/lib/resources/templates/web-app/template/src/types/global.d.ts +0 -9
- package/src/lib/resources/templates/web-app/template/src/vite-env.d.ts +0 -1
- package/src/lib/resources/templates/web-app/template/tailwind.config.js +0 -30
- package/src/lib/resources/templates/web-app/template/tsconfig.json +0 -37
- package/src/lib/resources/templates/web-app/template/tsconfig.node.json +0 -10
- package/src/lib/resources/templates/web-app/template/vite.config.ts +0 -23
- package/src/lib/resources/utils/background-monitor.js +0 -223
- package/src/lib/resources/utils/compression-metrics.js +0 -1031
- package/src/lib/resources/utils/compression-strategies.js +0 -557
- package/src/lib/resources/utils/content-normalizer.js +0 -455
- package/src/lib/resources/utils/context-compressor.js +0 -583
- package/src/lib/resources/utils/context-rule-parser.js +0 -180
- package/src/lib/resources/utils/context-token-monitor.js +0 -463
- package/src/lib/resources/utils/context-update-manager.js +0 -502
- package/src/lib/resources/utils/git-utils.js +0 -220
- package/src/lib/resources/utils/importance-classifier.js +0 -645
- package/src/lib/resources/utils/information-filter.js +0 -710
- package/src/lib/resources/utils/persona-aware-compressor.js +0 -598
- package/src/lib/resources/utils/prompt-injector.js +0 -242
- package/src/lib/resources/utils/simplified-evaluator.js +0 -119
- package/src/lib/resources/utils/text-summarizer.js +0 -492
- package/src/lib/resources/utils/token-counter.js +0 -177
- package/src/lib/resources/utils/token-monitor.js +0 -484
- package/src/lib/resources/utils/token-reporter.js +0 -452
- package/src/lib/resources/utils/token-storage.js +0 -433
- package/src/lib/resources/utils/token-tracker.js +0 -283
- package/src/lib/state/priority-calculator.js +0 -217
- package/src/lib/state/state-index.js +0 -155
- package/src/lib/state/task-scanner.js +0 -328
- package/src/lib/task-analyzer.js +0 -599
- package/src/lib/template-cache-system.js +0 -559
- package/src/lib/template-downloader.js +0 -467
- package/src/lib/template-version-manager.js +0 -491
- package/src/lib/token-optimizer.js +0 -517
- 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
|
-
}
|