aiwf 0.3.14 → 0.3.16
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.ko.md +19 -10
- package/README.md +54 -9
- package/docs/AI-WORKFLOW.ko.md +1 -1
- package/docs/AI-WORKFLOW.md +1 -1
- package/docs/API_REFERENCE_FULL.ko.md +979 -0
- package/docs/API_REFERENCE_FULL.md +979 -0
- package/docs/ARCHITECTURE.ko.md +314 -0
- package/docs/ARCHITECTURE.md +314 -0
- package/docs/GETTING_STARTED.ko.md +219 -0
- package/docs/GETTING_STARTED.md +1 -2
- package/docs/PERFORMANCE_GUIDELINES.ko.md +388 -0
- package/docs/ROADMAP_v0.4.0.md +286 -0
- package/docs/TROUBLESHOOTING.ko.md +366 -0
- package/package.json +50 -2
- package/src/DEPENDENCY_MAP.md +90 -0
- package/src/cli/index.js +1 -1
- package/src/lib/installer.js +204 -15
- package/src/lib/resources/templates/api-server/template/src/controllers/aiwfController.ts +0 -14
- package/src/lib/resources/templates/api-server/template/src/routes/aiwf.ts +0 -11
- package/src/lib/resources/templates/web-app/template/src/pages/AiwfDashboard.tsx +2 -5
- package/src/lib/resources/utils/token-reporter.js +2 -3
- package/src/utils/checkpoint-manager.js +11 -1
- package/src/utils/engineering-guard.js +11 -1
- package/src/utils/messages.js +16 -0
- package/src/utils/paths.js +2 -2
- package/templates/api-server/template/src/controllers/aiwfController.ts +0 -14
- package/templates/api-server/template/src/routes/aiwf.ts +0 -11
- package/templates/web-app/template/src/pages/AiwfDashboard.tsx +2 -5
- package/docs/CLEANUP_RECOMMENDATIONS.md +0 -86
- package/docs/git-feature-integration-guide.md +0 -253
- package/docs/guides/feature-git-integration-guide-ko.md +0 -481
- package/docs/guides/feature-git-integration-guide.md +0 -481
- package/docs/lightweight-evaluation.md +0 -198
- package/docs/performance-benchmark-report.md +0 -159
- package/docs/reports/S02-sprint-report.md +0 -190
- package/docs/usability-test-report.md +0 -170
- package/src/commands/cache-templates.js +0 -209
- package/src/commands/create-offline.js +0 -86
- package/src/commands/state-refactored.js +0 -419
- package/src/lib/resources/commands/feature-ledger.js +0 -565
- package/src/lib/resources/commands/feature_commit_report.js +0 -370
- package/src/lib/resources/commands/scan_git_history.js +0 -234
- package/src/lib/resources/commands/sync_feature_commits.js +0 -154
- package/src/lib/resources/templates/web-app/template/src/components/aiwf/FeatureLedger.tsx +0 -93
- package/src/lib/resources/utils/feature-updater.js +0 -271
- package/src/utils/git-integration.js +0 -173
- package/templates/web-app/template/src/components/aiwf/FeatureLedger.tsx +0 -93
- /package/docs/{AI_PERSONA_SYSTEM_DESIGN.md → designs/AI_PERSONA_SYSTEM_DESIGN.md} +0 -0
- /package/docs/{API_DOCUMENTATION.md → designs/API_DOCUMENTATION.md} +0 -0
- /package/docs/{API_REFERENCE.md → designs/API_REFERENCE.md} +0 -0
- /package/docs/{Enhanced_Installation_Flow_Design.md → designs/Enhanced_Installation_Flow_Design.md} +0 -0
- /package/docs/{moonklabs-metadata-system-prd.md → designs/aiwf-metadata-system-prd.md} +0 -0
- /package/docs/{offline-template-cache.md → designs/offline-template-cache.md} +0 -0
- /package/docs/{persona-aware-compression.md → designs/persona-aware-compression.md} +0 -0
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs-extra');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const https = require('https');
|
|
6
|
-
const tar = require('tar');
|
|
7
|
-
const chalk = require('chalk');
|
|
8
|
-
const ora = require('ora');
|
|
9
|
-
|
|
10
|
-
const CACHE_DIR = path.join(__dirname, '../.cache');
|
|
11
|
-
const TEMPLATES_DIR = path.join(__dirname, '../templates');
|
|
12
|
-
const DEPENDENCIES_CACHE = path.join(CACHE_DIR, 'dependencies');
|
|
13
|
-
|
|
14
|
-
// 각 템플릿의 주요 의존성 목록
|
|
15
|
-
const TEMPLATE_DEPENDENCIES = {
|
|
16
|
-
'web-app': [
|
|
17
|
-
'react@18',
|
|
18
|
-
'react-dom@18',
|
|
19
|
-
'vite@4',
|
|
20
|
-
'typescript@5',
|
|
21
|
-
'tailwindcss@3'
|
|
22
|
-
],
|
|
23
|
-
'api-server': [
|
|
24
|
-
'express@4',
|
|
25
|
-
'typescript@5',
|
|
26
|
-
'jsonwebtoken@9',
|
|
27
|
-
'swagger-ui-express@5'
|
|
28
|
-
],
|
|
29
|
-
'npm-library': [
|
|
30
|
-
'typescript@5',
|
|
31
|
-
'rollup@3',
|
|
32
|
-
'jest@29'
|
|
33
|
-
]
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
async function cacheTemplates() {
|
|
37
|
-
console.log(chalk.cyan('\n📦 AIWF 템플릿 캐시 시스템\n'));
|
|
38
|
-
|
|
39
|
-
const spinner = ora('캐시 디렉토리 준비 중...').start();
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
// 캐시 디렉토리 생성
|
|
43
|
-
await fs.ensureDir(CACHE_DIR);
|
|
44
|
-
await fs.ensureDir(DEPENDENCIES_CACHE);
|
|
45
|
-
|
|
46
|
-
spinner.text = '템플릿 압축 중...';
|
|
47
|
-
|
|
48
|
-
// 각 템플릿을 압축하여 캐시
|
|
49
|
-
const templates = await fs.readdir(TEMPLATES_DIR);
|
|
50
|
-
|
|
51
|
-
for (const template of templates) {
|
|
52
|
-
const templatePath = path.join(TEMPLATES_DIR, template);
|
|
53
|
-
const stat = await fs.stat(templatePath);
|
|
54
|
-
|
|
55
|
-
if (stat.isDirectory()) {
|
|
56
|
-
const cachePath = path.join(CACHE_DIR, `${template}.tar.gz`);
|
|
57
|
-
|
|
58
|
-
await tar.create(
|
|
59
|
-
{
|
|
60
|
-
gzip: true,
|
|
61
|
-
file: cachePath,
|
|
62
|
-
cwd: TEMPLATES_DIR
|
|
63
|
-
},
|
|
64
|
-
[template]
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
spinner.text = `${template} 템플릿 캐시 완료`;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
spinner.text = '템플릿 메타데이터 생성 중...';
|
|
72
|
-
|
|
73
|
-
// 캐시 메타데이터 생성
|
|
74
|
-
const metadata = {
|
|
75
|
-
version: require('../../package.json').version,
|
|
76
|
-
cachedAt: new Date().toISOString(),
|
|
77
|
-
templates: {},
|
|
78
|
-
dependencies: {}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
for (const template of templates) {
|
|
82
|
-
const configPath = path.join(TEMPLATES_DIR, template, 'config.json');
|
|
83
|
-
if (await fs.pathExists(configPath)) {
|
|
84
|
-
const config = await fs.readJson(configPath);
|
|
85
|
-
metadata.templates[template] = {
|
|
86
|
-
name: config.name,
|
|
87
|
-
displayName: config.displayName,
|
|
88
|
-
description: config.description,
|
|
89
|
-
size: (await fs.stat(path.join(CACHE_DIR, `${template}.tar.gz`))).size
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
await fs.writeJson(path.join(CACHE_DIR, 'metadata.json'), metadata, { spaces: 2 });
|
|
95
|
-
|
|
96
|
-
spinner.succeed('템플릿 캐시 완료!');
|
|
97
|
-
|
|
98
|
-
// 캐시 통계 출력
|
|
99
|
-
console.log(chalk.green('\n✅ 캐시 생성 완료\n'));
|
|
100
|
-
console.log('캐시된 템플릿:');
|
|
101
|
-
for (const [template, info] of Object.entries(metadata.templates)) {
|
|
102
|
-
console.log(chalk.gray(` - ${info.displayName}: ${(info.size / 1024).toFixed(2)} KB`));
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const totalSize = Object.values(metadata.templates)
|
|
106
|
-
.reduce((sum, info) => sum + info.size, 0);
|
|
107
|
-
console.log(chalk.gray(`\n총 캐시 크기: ${(totalSize / 1024 / 1024).toFixed(2)} MB`));
|
|
108
|
-
|
|
109
|
-
} catch (error) {
|
|
110
|
-
spinner.fail('캐시 생성 중 오류가 발생했습니다.');
|
|
111
|
-
console.error(chalk.red(error.message));
|
|
112
|
-
process.exit(1);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async function restoreFromCache(templateName, targetPath) {
|
|
117
|
-
const cachePath = path.join(CACHE_DIR, `${templateName}.tar.gz`);
|
|
118
|
-
|
|
119
|
-
if (!await fs.pathExists(cachePath)) {
|
|
120
|
-
throw new Error(`캐시된 템플릿을 찾을 수 없습니다: ${templateName}`);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
await tar.extract({
|
|
124
|
-
file: cachePath,
|
|
125
|
-
cwd: targetPath
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// 템플릿 내용을 상위 디렉토리로 이동
|
|
129
|
-
const extractedPath = path.join(targetPath, templateName, 'template');
|
|
130
|
-
const files = await fs.readdir(extractedPath);
|
|
131
|
-
|
|
132
|
-
for (const file of files) {
|
|
133
|
-
await fs.move(
|
|
134
|
-
path.join(extractedPath, file),
|
|
135
|
-
path.join(targetPath, file)
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// 임시 디렉토리 삭제
|
|
140
|
-
await fs.remove(path.join(targetPath, templateName));
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async function checkCache() {
|
|
144
|
-
const metadataPath = path.join(CACHE_DIR, 'metadata.json');
|
|
145
|
-
|
|
146
|
-
if (!await fs.pathExists(metadataPath)) {
|
|
147
|
-
return { valid: false, reason: '캐시가 존재하지 않습니다' };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const metadata = await fs.readJson(metadataPath);
|
|
151
|
-
const packageVersion = require('../../package.json').version;
|
|
152
|
-
|
|
153
|
-
if (metadata.version !== packageVersion) {
|
|
154
|
-
return { valid: false, reason: '버전이 일치하지 않습니다' };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// 캐시가 7일 이상 오래된 경우
|
|
158
|
-
const cacheAge = Date.now() - new Date(metadata.cachedAt).getTime();
|
|
159
|
-
const sevenDays = 7 * 24 * 60 * 60 * 1000;
|
|
160
|
-
|
|
161
|
-
if (cacheAge > sevenDays) {
|
|
162
|
-
return { valid: false, reason: '캐시가 오래되었습니다' };
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return { valid: true, metadata };
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// CLI 명령어 처리
|
|
169
|
-
if (require.main === module) {
|
|
170
|
-
const command = process.argv[2];
|
|
171
|
-
|
|
172
|
-
switch (command) {
|
|
173
|
-
case 'create':
|
|
174
|
-
cacheTemplates().catch(error => {
|
|
175
|
-
console.error(chalk.red('오류:', error.message));
|
|
176
|
-
process.exit(1);
|
|
177
|
-
});
|
|
178
|
-
break;
|
|
179
|
-
|
|
180
|
-
case 'check':
|
|
181
|
-
checkCache().then(result => {
|
|
182
|
-
if (result.valid) {
|
|
183
|
-
console.log(chalk.green('✅ 캐시가 유효합니다'));
|
|
184
|
-
console.log(chalk.gray(`생성일: ${result.metadata.cachedAt}`));
|
|
185
|
-
} else {
|
|
186
|
-
console.log(chalk.yellow(`⚠️ ${result.reason}`));
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
break;
|
|
190
|
-
|
|
191
|
-
case 'clear':
|
|
192
|
-
fs.remove(CACHE_DIR).then(() => {
|
|
193
|
-
console.log(chalk.green('✅ 캐시가 삭제되었습니다'));
|
|
194
|
-
});
|
|
195
|
-
break;
|
|
196
|
-
|
|
197
|
-
default:
|
|
198
|
-
console.log(`
|
|
199
|
-
사용법: node cache-templates.js [command]
|
|
200
|
-
|
|
201
|
-
명령어:
|
|
202
|
-
create 템플릿 캐시 생성
|
|
203
|
-
check 캐시 상태 확인
|
|
204
|
-
clear 캐시 삭제
|
|
205
|
-
`);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
module.exports = { cacheTemplates, restoreFromCache, checkCache };
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs-extra');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const chalk = require('chalk');
|
|
6
|
-
const ora = require('ora');
|
|
7
|
-
const { checkCache, restoreFromCache } = require('./cache-templates');
|
|
8
|
-
|
|
9
|
-
async function createProjectOffline(template, projectName, targetDir) {
|
|
10
|
-
const spinner = ora('오프라인 모드로 프로젝트 생성 중...').start();
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
// 캐시 확인
|
|
14
|
-
const cacheStatus = await checkCache();
|
|
15
|
-
|
|
16
|
-
if (!cacheStatus.valid) {
|
|
17
|
-
spinner.fail(`캐시 문제: ${cacheStatus.reason}`);
|
|
18
|
-
console.log(chalk.yellow('\n온라인 모드로 전환하거나 캐시를 업데이트하세요.'));
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
spinner.text = '캐시에서 템플릿 복원 중...';
|
|
23
|
-
|
|
24
|
-
// 캐시에서 템플릿 복원
|
|
25
|
-
await restoreFromCache(template, targetDir);
|
|
26
|
-
|
|
27
|
-
// 플레이스홀더 교체
|
|
28
|
-
const replacements = {
|
|
29
|
-
'{{projectName}}': projectName,
|
|
30
|
-
'{{createdAt}}': new Date().toISOString(),
|
|
31
|
-
'{{year}}': new Date().getFullYear().toString()
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
await replacePlaceholders(targetDir, replacements);
|
|
35
|
-
|
|
36
|
-
spinner.succeed('프로젝트가 오프라인 모드로 생성되었습니다!');
|
|
37
|
-
|
|
38
|
-
console.log(chalk.green(`\n✅ ${projectName} 프로젝트가 생성되었습니다!\n`));
|
|
39
|
-
console.log(chalk.yellow('⚠️ 오프라인 모드로 생성되었습니다.'));
|
|
40
|
-
console.log('인터넷 연결 후 다음 명령어를 실행하세요:\n');
|
|
41
|
-
console.log(chalk.cyan(` cd ${targetDir}`));
|
|
42
|
-
console.log(chalk.cyan(' npm install\n'));
|
|
43
|
-
|
|
44
|
-
return true;
|
|
45
|
-
|
|
46
|
-
} catch (error) {
|
|
47
|
-
spinner.fail('오프라인 프로젝트 생성 실패');
|
|
48
|
-
throw error;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function replacePlaceholders(dir, replacements) {
|
|
53
|
-
const files = await fs.readdir(dir);
|
|
54
|
-
|
|
55
|
-
for (const file of files) {
|
|
56
|
-
const filePath = path.join(dir, file);
|
|
57
|
-
const stat = await fs.stat(filePath);
|
|
58
|
-
|
|
59
|
-
if (stat.isDirectory() && file !== 'node_modules') {
|
|
60
|
-
await replacePlaceholders(filePath, replacements);
|
|
61
|
-
} else if (stat.isFile() && !file.endsWith('.jpg') && !file.endsWith('.png')) {
|
|
62
|
-
try {
|
|
63
|
-
let content = await fs.readFile(filePath, 'utf8');
|
|
64
|
-
|
|
65
|
-
for (const [placeholder, value] of Object.entries(replacements)) {
|
|
66
|
-
content = content.replace(new RegExp(placeholder, 'g'), value);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
await fs.writeFile(filePath, content, 'utf8');
|
|
70
|
-
} catch (error) {
|
|
71
|
-
// 바이너리 파일 무시
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// 네트워크 연결 확인
|
|
78
|
-
function isOnline() {
|
|
79
|
-
return new Promise((resolve) => {
|
|
80
|
-
require('dns').lookup('registry.npmjs.org', (err) => {
|
|
81
|
-
resolve(!err);
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
module.exports = { createProjectOffline, isOnline };
|
|
@@ -1,419 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* AIWF State Management Command (Refactored)
|
|
5
|
-
* 모듈화된 구조로 중앙 상태 인덱스를 통한 프로젝트 상태 관리
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import fs from 'fs/promises';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import chalk from 'chalk';
|
|
11
|
-
import ora from 'ora';
|
|
12
|
-
|
|
13
|
-
// 분할된 모듈들 import
|
|
14
|
-
import { StateIndexManager } from '../lib/state/state-index.js';
|
|
15
|
-
import { PriorityCalculator } from '../lib/state/priority-calculator.js';
|
|
16
|
-
import { TaskScanner } from '../lib/state/task-scanner.js';
|
|
17
|
-
|
|
18
|
-
export default class StateCommand {
|
|
19
|
-
constructor() {
|
|
20
|
-
this.projectRoot = null;
|
|
21
|
-
this.aiwfPath = null;
|
|
22
|
-
this.stateIndexManager = null;
|
|
23
|
-
this.priorityCalculator = null;
|
|
24
|
-
this.taskScanner = null;
|
|
25
|
-
this.workflowRules = null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async execute(args) {
|
|
29
|
-
const subcommand = args[0] || 'show';
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
// 프로젝트 루트 찾기 및 모듈 초기화
|
|
33
|
-
await this.initializeModules();
|
|
34
|
-
|
|
35
|
-
switch (subcommand) {
|
|
36
|
-
case 'update':
|
|
37
|
-
await this.updateStateIndex();
|
|
38
|
-
break;
|
|
39
|
-
case 'show':
|
|
40
|
-
await this.showState();
|
|
41
|
-
break;
|
|
42
|
-
case 'focus':
|
|
43
|
-
await this.focusTask(args[1]);
|
|
44
|
-
break;
|
|
45
|
-
case 'complete':
|
|
46
|
-
await this.completeTask(args[1]);
|
|
47
|
-
break;
|
|
48
|
-
case 'next':
|
|
49
|
-
await this.suggestNext();
|
|
50
|
-
break;
|
|
51
|
-
case 'init':
|
|
52
|
-
await this.initStateIndex();
|
|
53
|
-
break;
|
|
54
|
-
case 'validate':
|
|
55
|
-
await this.validateCommand();
|
|
56
|
-
break;
|
|
57
|
-
default:
|
|
58
|
-
console.error(chalk.red(`Unknown subcommand: ${subcommand}`));
|
|
59
|
-
this.showHelp();
|
|
60
|
-
}
|
|
61
|
-
} catch (error) {
|
|
62
|
-
console.error(chalk.red(`Error: ${error.message}`));
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* 모듈들 초기화
|
|
69
|
-
*/
|
|
70
|
-
async initializeModules() {
|
|
71
|
-
this.projectRoot = await this.findProjectRoot();
|
|
72
|
-
this.aiwfPath = path.join(this.projectRoot, '.aiwf');
|
|
73
|
-
|
|
74
|
-
this.stateIndexManager = new StateIndexManager(this.aiwfPath);
|
|
75
|
-
this.taskScanner = new TaskScanner(this.aiwfPath);
|
|
76
|
-
|
|
77
|
-
// 워크플로우 규칙 로드
|
|
78
|
-
this.workflowRules = await this.stateIndexManager.loadWorkflowRules();
|
|
79
|
-
this.priorityCalculator = new PriorityCalculator(this.workflowRules);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async findProjectRoot(startDir = process.cwd()) {
|
|
83
|
-
let currentDir = startDir;
|
|
84
|
-
|
|
85
|
-
while (currentDir !== path.parse(currentDir).root) {
|
|
86
|
-
try {
|
|
87
|
-
const aiwfPath = path.join(currentDir, '.aiwf');
|
|
88
|
-
await fs.access(aiwfPath);
|
|
89
|
-
return currentDir;
|
|
90
|
-
} catch {
|
|
91
|
-
currentDir = path.dirname(currentDir);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
throw new Error('.aiwf directory not found. Are you in an AIWF project?');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* 상태 인덱스 초기화
|
|
100
|
-
*/
|
|
101
|
-
async initStateIndex() {
|
|
102
|
-
const spinner = ora('Initializing state index...').start();
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
const result = await this.stateIndexManager.initializeIndex(this.projectRoot);
|
|
106
|
-
|
|
107
|
-
if (result.success) {
|
|
108
|
-
spinner.succeed(result.message);
|
|
109
|
-
} else {
|
|
110
|
-
spinner.warn(result.message);
|
|
111
|
-
}
|
|
112
|
-
} catch (error) {
|
|
113
|
-
spinner.fail(`Failed to initialize state index: ${error.message}`);
|
|
114
|
-
throw error;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* 상태 인덱스 업데이트
|
|
120
|
-
*/
|
|
121
|
-
async updateStateIndex() {
|
|
122
|
-
const spinner = ora('Updating state index...').start();
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
let stateIndex = await this.stateIndexManager.loadIndex();
|
|
126
|
-
|
|
127
|
-
if (!stateIndex) {
|
|
128
|
-
spinner.info('State index not found. Creating new one...');
|
|
129
|
-
await this.initStateIndex();
|
|
130
|
-
stateIndex = await this.stateIndexManager.loadIndex();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// 1. 스프린트 데이터 스캔
|
|
134
|
-
spinner.text = 'Scanning sprint directories...';
|
|
135
|
-
const sprintData = await this.taskScanner.scanSprintDirectories();
|
|
136
|
-
|
|
137
|
-
// 2. 일반 태스크 스캔
|
|
138
|
-
spinner.text = 'Scanning general tasks...';
|
|
139
|
-
const generalTasks = await this.taskScanner.scanGeneralTasks();
|
|
140
|
-
|
|
141
|
-
// 3. 태스크 데이터 통합
|
|
142
|
-
spinner.text = 'Consolidating task data...';
|
|
143
|
-
const allTasks = {};
|
|
144
|
-
|
|
145
|
-
// 스프린트 태스크들 추가
|
|
146
|
-
for (const [sprintId, sprint] of Object.entries(sprintData)) {
|
|
147
|
-
Object.assign(allTasks, sprint.tasks);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// 일반 태스크들 추가
|
|
151
|
-
Object.assign(allTasks, generalTasks);
|
|
152
|
-
|
|
153
|
-
// 4. 우선순위 계산
|
|
154
|
-
spinner.text = 'Calculating priorities...';
|
|
155
|
-
stateIndex.tasks = this.priorityCalculator.recalculateAllPriorities({
|
|
156
|
-
...stateIndex,
|
|
157
|
-
tasks: allTasks
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
// 5. 스프린트 요약 업데이트
|
|
161
|
-
spinner.text = 'Updating sprint summaries...';
|
|
162
|
-
stateIndex.sprint_summary = this.generateSprintSummaries(sprintData, stateIndex.tasks);
|
|
163
|
-
|
|
164
|
-
// 6. 프로젝트 메트릭스 업데이트
|
|
165
|
-
spinner.text = 'Updating project metrics...';
|
|
166
|
-
stateIndex.metrics = this.calculateProjectMetrics(stateIndex.tasks);
|
|
167
|
-
|
|
168
|
-
// 7. 다음 작업 제안 생성
|
|
169
|
-
spinner.text = 'Generating next action suggestions...';
|
|
170
|
-
stateIndex.next_actions = this.generateNextActions(stateIndex);
|
|
171
|
-
|
|
172
|
-
// 8. 프로젝트 정보 업데이트
|
|
173
|
-
await this.updateProjectInfo(stateIndex);
|
|
174
|
-
|
|
175
|
-
// 9. 상태 인덱스 저장
|
|
176
|
-
await this.stateIndexManager.saveIndex(stateIndex);
|
|
177
|
-
|
|
178
|
-
spinner.succeed(`State index updated successfully (${Object.keys(stateIndex.tasks).length} tasks processed)`);
|
|
179
|
-
|
|
180
|
-
// 간단한 요약 출력
|
|
181
|
-
this.printUpdateSummary(stateIndex);
|
|
182
|
-
|
|
183
|
-
} catch (error) {
|
|
184
|
-
spinner.fail(`Failed to update state index: ${error.message}`);
|
|
185
|
-
throw error;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* 스프린트 요약 생성
|
|
191
|
-
*/
|
|
192
|
-
generateSprintSummaries(sprintData, tasks) {
|
|
193
|
-
const summaries = {};
|
|
194
|
-
|
|
195
|
-
for (const [sprintId, sprint] of Object.entries(sprintData)) {
|
|
196
|
-
const sprintTasks = Object.values(tasks).filter(task => task.sprint_id === sprintId);
|
|
197
|
-
const completedTasks = sprintTasks.filter(task => task.status === 'completed');
|
|
198
|
-
|
|
199
|
-
summaries[sprintId] = {
|
|
200
|
-
...sprint.meta,
|
|
201
|
-
total_tasks: sprintTasks.length,
|
|
202
|
-
completed_tasks: completedTasks.length,
|
|
203
|
-
progress: sprintTasks.length > 0 ? completedTasks.length / sprintTasks.length : 0,
|
|
204
|
-
avg_priority: sprintTasks.length > 0
|
|
205
|
-
? sprintTasks.reduce((sum, task) => sum + (task.priority_score || 0), 0) / sprintTasks.length
|
|
206
|
-
: 0,
|
|
207
|
-
path: sprint.path
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return summaries;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* 프로젝트 메트릭스 계산
|
|
216
|
-
*/
|
|
217
|
-
calculateProjectMetrics(tasks) {
|
|
218
|
-
const taskArray = Object.values(tasks);
|
|
219
|
-
const completed = taskArray.filter(t => t.status === 'completed');
|
|
220
|
-
const inProgress = taskArray.filter(t => t.status === 'in_progress');
|
|
221
|
-
const pending = taskArray.filter(t => t.status === 'pending');
|
|
222
|
-
const blocked = taskArray.filter(t => t.status === 'blocked');
|
|
223
|
-
|
|
224
|
-
return {
|
|
225
|
-
total_tasks: taskArray.length,
|
|
226
|
-
completed_tasks: completed.length,
|
|
227
|
-
in_progress_tasks: inProgress.length,
|
|
228
|
-
pending_tasks: pending.length,
|
|
229
|
-
blocked_tasks: blocked.length,
|
|
230
|
-
overall_progress: taskArray.length > 0 ? completed.length / taskArray.length : 0,
|
|
231
|
-
avg_priority: taskArray.length > 0
|
|
232
|
-
? taskArray.reduce((sum, task) => sum + (task.priority_score || 0), 0) / taskArray.length
|
|
233
|
-
: 0,
|
|
234
|
-
last_calculated: new Date().toISOString()
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* 다음 작업 제안 생성
|
|
240
|
-
*/
|
|
241
|
-
generateNextActions(stateIndex) {
|
|
242
|
-
const tasks = Object.values(stateIndex.tasks);
|
|
243
|
-
const availableTasks = tasks.filter(task =>
|
|
244
|
-
task.status === 'pending' || task.status === 'in_progress'
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
// 우선순위순으로 정렬
|
|
248
|
-
availableTasks.sort((a, b) => (b.priority_score || 0) - (a.priority_score || 0));
|
|
249
|
-
|
|
250
|
-
const suggestions = [];
|
|
251
|
-
|
|
252
|
-
// 최고 우선순위 태스크들
|
|
253
|
-
const topTasks = availableTasks.slice(0, 3);
|
|
254
|
-
for (const task of topTasks) {
|
|
255
|
-
suggestions.push({
|
|
256
|
-
type: 'high-priority',
|
|
257
|
-
task_id: task.id,
|
|
258
|
-
reason: `High priority (${task.priority_score?.toFixed(2)})`,
|
|
259
|
-
action: `Focus on ${task.title}`
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// 차단된 태스크들 확인
|
|
264
|
-
const blockedTasks = tasks.filter(task => task.status === 'blocked');
|
|
265
|
-
for (const task of blockedTasks.slice(0, 2)) {
|
|
266
|
-
suggestions.push({
|
|
267
|
-
type: 'unblock',
|
|
268
|
-
task_id: task.id,
|
|
269
|
-
reason: 'Currently blocked - needs attention',
|
|
270
|
-
action: `Resolve blocking issues for ${task.title}`
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return suggestions.slice(0, 5); // 최대 5개 제안
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* 현재 상태 표시
|
|
279
|
-
*/
|
|
280
|
-
async showState() {
|
|
281
|
-
const spinner = ora('Loading state...').start();
|
|
282
|
-
|
|
283
|
-
try {
|
|
284
|
-
const stateIndex = await this.stateIndexManager.loadIndex();
|
|
285
|
-
|
|
286
|
-
if (!stateIndex) {
|
|
287
|
-
spinner.fail('State index not found. Run "aiwf state init" first.');
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
spinner.succeed('State loaded successfully');
|
|
292
|
-
|
|
293
|
-
// 상태 정보 출력
|
|
294
|
-
console.log(chalk.cyan('\n📊 AIWF Project State Dashboard'));
|
|
295
|
-
console.log(chalk.gray('='.repeat(50)));
|
|
296
|
-
|
|
297
|
-
// 프로젝트 기본 정보
|
|
298
|
-
console.log(chalk.bold('\n🏗️ Project Info:'));
|
|
299
|
-
console.log(` Name: ${stateIndex.project_info?.name || 'Unknown'}`);
|
|
300
|
-
console.log(` Phase: ${stateIndex.project_info?.phase || 'Unknown'}`);
|
|
301
|
-
console.log(` Current Milestone: ${stateIndex.project_info?.current_milestone || 'None'}`);
|
|
302
|
-
console.log(` Current Sprint: ${stateIndex.project_info?.current_sprint || 'None'}`);
|
|
303
|
-
|
|
304
|
-
// 메트릭스
|
|
305
|
-
const metrics = stateIndex.metrics;
|
|
306
|
-
console.log(chalk.bold('\n📈 Metrics:'));
|
|
307
|
-
console.log(` Total Tasks: ${metrics?.total_tasks || 0}`);
|
|
308
|
-
console.log(` Completed: ${chalk.green(metrics?.completed_tasks || 0)}`);
|
|
309
|
-
console.log(` In Progress: ${chalk.yellow(metrics?.in_progress_tasks || 0)}`);
|
|
310
|
-
console.log(` Pending: ${chalk.gray(metrics?.pending_tasks || 0)}`);
|
|
311
|
-
console.log(` Blocked: ${chalk.red(metrics?.blocked_tasks || 0)}`);
|
|
312
|
-
console.log(` Overall Progress: ${chalk.cyan((metrics?.overall_progress * 100 || 0).toFixed(1))}%`);
|
|
313
|
-
|
|
314
|
-
// 현재 포커스
|
|
315
|
-
if (stateIndex.current_focus?.primary_task) {
|
|
316
|
-
console.log(chalk.bold('\n🎯 Current Focus:'));
|
|
317
|
-
console.log(` Primary: ${stateIndex.current_focus.primary_task}`);
|
|
318
|
-
if (stateIndex.current_focus.secondary_tasks?.length > 0) {
|
|
319
|
-
console.log(` Secondary: ${stateIndex.current_focus.secondary_tasks.join(', ')}`);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// 다음 작업 제안
|
|
324
|
-
if (stateIndex.next_actions?.length > 0) {
|
|
325
|
-
console.log(chalk.bold('\n🚀 Suggested Next Actions:'));
|
|
326
|
-
stateIndex.next_actions.slice(0, 3).forEach((action, index) => {
|
|
327
|
-
console.log(` ${index + 1}. ${action.action} ${chalk.gray(`(${action.reason})`)}`);
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
console.log(chalk.gray(`\nLast updated: ${stateIndex.last_updated}`));
|
|
332
|
-
|
|
333
|
-
} catch (error) {
|
|
334
|
-
spinner.fail(`Failed to show state: ${error.message}`);
|
|
335
|
-
throw error;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* 업데이트 요약 출력
|
|
341
|
-
*/
|
|
342
|
-
printUpdateSummary(stateIndex) {
|
|
343
|
-
const metrics = stateIndex.metrics;
|
|
344
|
-
console.log(chalk.cyan('\n📊 Update Summary:'));
|
|
345
|
-
console.log(` ${chalk.green('✅')} ${metrics.completed_tasks} completed tasks`);
|
|
346
|
-
console.log(` ${chalk.yellow('🔄')} ${metrics.in_progress_tasks} in progress`);
|
|
347
|
-
console.log(` ${chalk.gray('⏳')} ${metrics.pending_tasks} pending tasks`);
|
|
348
|
-
if (metrics.blocked_tasks > 0) {
|
|
349
|
-
console.log(` ${chalk.red('❌')} ${metrics.blocked_tasks} blocked tasks`);
|
|
350
|
-
}
|
|
351
|
-
console.log(` ${chalk.cyan('📈')} ${(metrics.overall_progress * 100).toFixed(1)}% overall progress`);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* 프로젝트 정보 업데이트
|
|
356
|
-
*/
|
|
357
|
-
async updateProjectInfo(stateIndex) {
|
|
358
|
-
try {
|
|
359
|
-
// PROJECT_MANIFEST.md에서 현재 마일스톤/스프린트 정보 읽기
|
|
360
|
-
const manifestPath = path.join(this.aiwfPath, '00_PROJECT_MANIFEST.md');
|
|
361
|
-
const manifestContent = await fs.readFile(manifestPath, 'utf8');
|
|
362
|
-
|
|
363
|
-
// 현재 마일스톤 추출
|
|
364
|
-
const milestoneMatch = manifestContent.match(/current_milestone:\s*([^\n]+)/i);
|
|
365
|
-
if (milestoneMatch) {
|
|
366
|
-
stateIndex.project_info.current_milestone = milestoneMatch[1].trim();
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// 현재 스프린트 추출
|
|
370
|
-
const sprintMatch = manifestContent.match(/current_sprint:\s*([^\n]+)/i);
|
|
371
|
-
if (sprintMatch) {
|
|
372
|
-
stateIndex.project_info.current_sprint = sprintMatch[1].trim();
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
} catch (error) {
|
|
376
|
-
// 매니페스트 파일이 없거나 읽을 수 없으면 무시
|
|
377
|
-
console.warn(`Could not update project info from manifest: ${error.message}`);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* 기타 메서드들 (focusTask, completeTask, suggestNext, validateCommand 등)
|
|
383
|
-
* 기존 state.js에서 가져와야 함
|
|
384
|
-
*/
|
|
385
|
-
async focusTask(taskId) {
|
|
386
|
-
// 구현 필요
|
|
387
|
-
console.log(chalk.yellow('focusTask method needs implementation'));
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
async completeTask(taskId) {
|
|
391
|
-
// 구현 필요
|
|
392
|
-
console.log(chalk.yellow('completeTask method needs implementation'));
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
async suggestNext() {
|
|
396
|
-
// 구현 필요
|
|
397
|
-
console.log(chalk.yellow('suggestNext method needs implementation'));
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
async validateCommand() {
|
|
401
|
-
// 구현 필요
|
|
402
|
-
console.log(chalk.yellow('validateCommand method needs implementation'));
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
showHelp() {
|
|
406
|
-
console.log(chalk.cyan('\n🤖 AIWF State Management'));
|
|
407
|
-
console.log(chalk.gray('-'.repeat(50)));
|
|
408
|
-
console.log('Usage: aiwf state [command]');
|
|
409
|
-
console.log('');
|
|
410
|
-
console.log('Commands:');
|
|
411
|
-
console.log(' init Initialize state index');
|
|
412
|
-
console.log(' update Update state index');
|
|
413
|
-
console.log(' show Show current state');
|
|
414
|
-
console.log(' focus Focus on a specific task');
|
|
415
|
-
console.log(' complete Mark task as completed');
|
|
416
|
-
console.log(' next Suggest next actions');
|
|
417
|
-
console.log(' validate Validate state consistency');
|
|
418
|
-
}
|
|
419
|
-
}
|