aiwf 0.3.15 → 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.
Files changed (35) hide show
  1. package/README.ko.md +3 -4
  2. package/README.md +38 -3
  3. package/docs/API_REFERENCE_FULL.ko.md +979 -0
  4. package/docs/API_REFERENCE_FULL.md +979 -0
  5. package/docs/ARCHITECTURE.ko.md +314 -0
  6. package/docs/ARCHITECTURE.md +314 -0
  7. package/docs/GETTING_STARTED.ko.md +219 -0
  8. package/docs/GETTING_STARTED.md +0 -1
  9. package/docs/PERFORMANCE_GUIDELINES.ko.md +388 -0
  10. package/docs/TROUBLESHOOTING.ko.md +366 -0
  11. package/package.json +50 -2
  12. package/src/DEPENDENCY_MAP.md +90 -0
  13. package/src/lib/installer.js +1 -0
  14. package/src/lib/resources/templates/api-server/template/src/controllers/aiwfController.ts +0 -14
  15. package/src/lib/resources/templates/api-server/template/src/routes/aiwf.ts +0 -11
  16. package/src/lib/resources/templates/web-app/template/src/pages/AiwfDashboard.tsx +2 -5
  17. package/src/lib/resources/utils/token-reporter.js +2 -3
  18. package/src/utils/checkpoint-manager.js +11 -1
  19. package/src/utils/engineering-guard.js +11 -1
  20. package/templates/api-server/template/src/controllers/aiwfController.ts +0 -14
  21. package/templates/api-server/template/src/routes/aiwf.ts +0 -11
  22. package/templates/web-app/template/src/pages/AiwfDashboard.tsx +2 -5
  23. package/docs/guides/feature-git-integration-guide-ko.md +0 -481
  24. package/docs/guides/feature-git-integration-guide.md +0 -481
  25. package/src/commands/cache-templates.js +0 -209
  26. package/src/commands/create-offline.js +0 -86
  27. package/src/commands/state-refactored.js +0 -419
  28. package/src/lib/resources/commands/feature-ledger.js +0 -565
  29. package/src/lib/resources/commands/feature_commit_report.js +0 -370
  30. package/src/lib/resources/commands/scan_git_history.js +0 -234
  31. package/src/lib/resources/commands/sync_feature_commits.js +0 -154
  32. package/src/lib/resources/templates/web-app/template/src/components/aiwf/FeatureLedger.tsx +0 -93
  33. package/src/lib/resources/utils/feature-updater.js +0 -271
  34. package/src/utils/git-integration.js +0 -173
  35. package/templates/web-app/template/src/components/aiwf/FeatureLedger.tsx +0 -93
@@ -1,154 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const { Command } = require('commander');
4
- const chalk = require('chalk');
5
- const ora = require('ora');
6
- const {
7
- syncFeatureWithGit,
8
- syncAllActiveFeatures,
9
- findFeatureFile
10
- } = require('../utils/feature-updater');
11
- const {
12
- getFeatureRelatedCommits,
13
- isGitRepository,
14
- getRecentFeatureCommits
15
- } = require('../utils/git-utils');
16
-
17
- const program = new Command();
18
-
19
- /**
20
- * sync_feature_commits 명령어
21
- * Feature Ledger와 Git 커밋을 동기화
22
- */
23
-
24
- program
25
- .name('sync_feature_commits')
26
- .description('Feature Ledger와 Git 커밋 히스토리를 동기화합니다')
27
- .argument('[featureId]', 'Feature ID (예: FL001). 생략 시 모든 활성 Feature 동기화')
28
- .option('-s, --since <date>', '특정 날짜 이후의 커밋만 동기화 (YYYY-MM-DD)')
29
- .option('-r, --recent <count>', '최근 N개의 커밋에서 Feature 관련 커밋 찾기', '50')
30
- .option('-a, --all', '모든 활성 Feature를 동기화')
31
- .option('-v, --verbose', '상세 로그 출력')
32
- .action(async (featureId, options) => {
33
- const spinner = ora();
34
-
35
- try {
36
- // Git 저장소 확인
37
- if (!await isGitRepository()) {
38
- console.error(chalk.red('❌ 현재 디렉토리는 Git 저장소가 아닙니다.'));
39
- process.exit(1);
40
- }
41
-
42
- // 특정 Feature ID가 제공된 경우
43
- if (featureId && !options.all) {
44
- spinner.start(`${featureId} Feature와 Git 커밋 동기화 중...`);
45
-
46
- // Feature 파일 존재 확인
47
- const featureFile = await findFeatureFile(featureId);
48
- if (!featureFile) {
49
- spinner.fail(chalk.red(`Feature 파일을 찾을 수 없습니다: ${featureId}`));
50
- process.exit(1);
51
- }
52
-
53
- const result = await syncFeatureWithGit(featureId);
54
-
55
- if (result.success) {
56
- spinner.succeed(chalk.green(
57
- `✅ ${featureId} 동기화 완료! (${result.commitCount}개 커밋 연결됨)`
58
- ));
59
-
60
- if (options.verbose && result.commitCount > 0) {
61
- console.log(chalk.gray(`\n연결된 커밋 수: ${result.commitCount}`));
62
- }
63
- } else {
64
- spinner.fail(chalk.red(`❌ ${featureId} 동기화 실패`));
65
- process.exit(1);
66
- }
67
- }
68
- // 모든 활성 Feature 동기화
69
- else if (options.all || !featureId) {
70
- spinner.start('모든 활성 Feature와 Git 커밋 동기화 중...');
71
-
72
- const results = await syncAllActiveFeatures();
73
-
74
- spinner.succeed(chalk.green('✅ 동기화 완료!'));
75
- console.log(chalk.cyan(`\n📊 동기화 결과:`));
76
- console.log(chalk.white(` 총 Feature: ${results.total}`));
77
- console.log(chalk.green(` 성공: ${results.synced}`));
78
- if (results.failed > 0) {
79
- console.log(chalk.red(` 실패: ${results.failed}`));
80
- }
81
- }
82
-
83
- // 최근 커밋에서 Feature 관련 커밋 찾기 (옵션)
84
- if (options.recent) {
85
- spinner.start(`최근 ${options.recent}개 커밋에서 Feature 관련 커밋 검색 중...`);
86
-
87
- const recentCommits = await getRecentFeatureCommits(parseInt(options.recent));
88
-
89
- if (recentCommits.length > 0) {
90
- spinner.succeed(chalk.green(`${recentCommits.length}개의 Feature 관련 커밋 발견`));
91
-
92
- if (options.verbose) {
93
- console.log(chalk.cyan('\n📝 Feature 관련 커밋:'));
94
- recentCommits.forEach(commit => {
95
- const shortHash = commit.hash.substring(0, 7);
96
- const features = commit.featureIds.join(', ');
97
- console.log(chalk.gray(` ${shortHash} - ${features}: ${commit.message.split('\n')[0]}`));
98
- });
99
- }
100
- } else {
101
- spinner.info(chalk.yellow('최근 커밋에서 Feature 관련 커밋을 찾을 수 없습니다.'));
102
- }
103
- }
104
-
105
- // 특정 날짜 이후 커밋 검색 (옵션)
106
- if (options.since) {
107
- spinner.start(`${options.since} 이후의 Feature 관련 커밋 검색 중...`);
108
-
109
- const commits = await getFeatureRelatedCommits(options.since);
110
-
111
- if (commits.length > 0) {
112
- spinner.succeed(chalk.green(`${commits.length}개의 Feature 관련 커밋 발견`));
113
-
114
- // Feature별로 그룹화
115
- const featureGroups = {};
116
- commits.forEach(commit => {
117
- commit.featureIds.forEach(featureId => {
118
- if (!featureGroups[featureId]) {
119
- featureGroups[featureId] = [];
120
- }
121
- featureGroups[featureId].push(commit);
122
- });
123
- });
124
-
125
- console.log(chalk.cyan('\n📊 Feature별 커밋 통계:'));
126
- Object.entries(featureGroups).forEach(([featureId, commits]) => {
127
- console.log(chalk.white(` ${featureId}: ${commits.length}개 커밋`));
128
- });
129
- } else {
130
- spinner.info(chalk.yellow(`${options.since} 이후 Feature 관련 커밋이 없습니다.`));
131
- }
132
- }
133
-
134
- // 도움말 메시지
135
- if (!featureId && !options.all && !options.recent && !options.since) {
136
- console.log(chalk.cyan('\n💡 사용 예시:'));
137
- console.log(chalk.gray(' sync_feature_commits FL001 # 특정 Feature 동기화'));
138
- console.log(chalk.gray(' sync_feature_commits --all # 모든 활성 Feature 동기화'));
139
- console.log(chalk.gray(' sync_feature_commits --recent 100 # 최근 100개 커밋에서 Feature 찾기'));
140
- console.log(chalk.gray(' sync_feature_commits --since 2025-01-01 # 특정 날짜 이후 커밋 검색'));
141
- }
142
-
143
- } catch (error) {
144
- spinner.fail(chalk.red('동기화 중 오류 발생'));
145
- console.error(chalk.red(error.message));
146
- if (options.verbose) {
147
- console.error(error.stack);
148
- }
149
- process.exit(1);
150
- }
151
- });
152
-
153
- // 명령어 실행
154
- program.parse(process.argv);
@@ -1,93 +0,0 @@
1
- interface Feature {
2
- id: string
3
- name: string
4
- description: string
5
- status: 'completed' | 'in_progress' | 'planned'
6
- type: string
7
- created_at: string
8
- completed_at?: string
9
- tags: string[]
10
- }
11
-
12
- interface FeatureLedgerProps {
13
- features: Feature[]
14
- }
15
-
16
- export default function FeatureLedger({ features }: FeatureLedgerProps) {
17
- const getStatusBadge = (status: Feature['status']) => {
18
- const styles = {
19
- completed: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
20
- in_progress: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
21
- planned: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300',
22
- }
23
-
24
- const labels = {
25
- completed: '완료',
26
- in_progress: '진행중',
27
- planned: '계획됨',
28
- }
29
-
30
- return (
31
- <span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${styles[status]}`}>
32
- {labels[status]}
33
- </span>
34
- )
35
- }
36
-
37
- return (
38
- <div className="bg-white dark:bg-gray-800 shadow overflow-hidden sm:rounded-lg">
39
- <div className="px-4 py-5 sm:px-6">
40
- <h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
41
- Feature Ledger
42
- </h3>
43
- <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
44
- 프로젝트에서 개발된 모든 기능을 추적합니다.
45
- </p>
46
- </div>
47
- <div className="border-t border-gray-200 dark:border-gray-700">
48
- <ul className="divide-y divide-gray-200 dark:divide-gray-700">
49
- {features.map((feature) => (
50
- <li key={feature.id} className="px-4 py-4 sm:px-6">
51
- <div className="flex items-start justify-between">
52
- <div className="flex-1">
53
- <div className="flex items-center justify-between">
54
- <p className="text-sm font-medium text-gray-900 dark:text-white truncate">
55
- {feature.name}
56
- </p>
57
- {getStatusBadge(feature.status)}
58
- </div>
59
- <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
60
- {feature.description}
61
- </p>
62
- <div className="mt-2 flex items-center text-xs text-gray-500 dark:text-gray-400">
63
- <span>{feature.id}</span>
64
- <span className="mx-2">•</span>
65
- <span>{new Date(feature.created_at).toLocaleDateString('ko-KR')}</span>
66
- {feature.completed_at && (
67
- <>
68
- <span className="mx-2">→</span>
69
- <span>{new Date(feature.completed_at).toLocaleDateString('ko-KR')}</span>
70
- </>
71
- )}
72
- </div>
73
- {feature.tags.length > 0 && (
74
- <div className="mt-2 flex flex-wrap gap-1">
75
- {feature.tags.map((tag) => (
76
- <span
77
- key={tag}
78
- className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300"
79
- >
80
- {tag}
81
- </span>
82
- ))}
83
- </div>
84
- )}
85
- </div>
86
- </div>
87
- </li>
88
- ))}
89
- </ul>
90
- </div>
91
- </div>
92
- )
93
- }
@@ -1,271 +0,0 @@
1
- const fs = require('fs').promises;
2
- const path = require('path');
3
- const yaml = require('yaml');
4
- const { getCommitsByFeatureId } = require('./git-utils');
5
-
6
- /**
7
- * Feature Ledger 파일 업데이트 메커니즘
8
- */
9
-
10
- /**
11
- * Feature 파일 경로 구성
12
- * @param {string} featureId - Feature ID (예: 'FL001')
13
- * @param {string} status - Feature 상태 (active, completed, archived)
14
- * @returns {string} - Feature 파일 경로
15
- */
16
- function getFeatureFilePath(featureId, status = 'active') {
17
- const basePath = path.join(process.cwd(), '.aiwf', '06_FEATURE_LEDGERS');
18
- const fileName = `${featureId}_*.md`;
19
- return path.join(basePath, status, fileName);
20
- }
21
-
22
- /**
23
- * Feature 파일 찾기
24
- * @param {string} featureId - Feature ID
25
- * @returns {Promise<{path: string, status: string}|null>} - 파일 경로와 상태
26
- */
27
- async function findFeatureFile(featureId) {
28
- const basePath = path.join(process.cwd(), '.aiwf', '06_FEATURE_LEDGERS');
29
- const statuses = ['active', 'completed', 'archived'];
30
-
31
- for (const status of statuses) {
32
- const dirPath = path.join(basePath, status);
33
- try {
34
- const files = await fs.readdir(dirPath);
35
- const featureFile = files.find(file => file.startsWith(`${featureId}_`));
36
- if (featureFile) {
37
- return {
38
- path: path.join(dirPath, featureFile),
39
- status: status
40
- };
41
- }
42
- } catch (error) {
43
- // 디렉토리가 없을 수 있음
44
- continue;
45
- }
46
- }
47
-
48
- return null;
49
- }
50
-
51
- /**
52
- * Feature 파일 읽기
53
- * @param {string} filePath - 파일 경로
54
- * @returns {Promise<{frontmatter: Object, content: string}>}
55
- */
56
- async function readFeatureFile(filePath) {
57
- const fileContent = await fs.readFile(filePath, 'utf8');
58
- const lines = fileContent.split('\n');
59
-
60
- // Frontmatter 파싱
61
- let frontmatterEnd = -1;
62
- let inFrontmatter = false;
63
- for (let i = 0; i < lines.length; i++) {
64
- if (lines[i] === '---') {
65
- if (!inFrontmatter) {
66
- inFrontmatter = true;
67
- } else {
68
- frontmatterEnd = i;
69
- break;
70
- }
71
- }
72
- }
73
-
74
- const frontmatterText = lines.slice(1, frontmatterEnd).join('\n');
75
- const frontmatter = yaml.parse(frontmatterText);
76
- const content = lines.slice(frontmatterEnd + 1).join('\n');
77
-
78
- return { frontmatter, content };
79
- }
80
-
81
- /**
82
- * Feature 파일 작성
83
- * @param {string} filePath - 파일 경로
84
- * @param {Object} frontmatter - Frontmatter 데이터
85
- * @param {string} content - 파일 내용
86
- */
87
- async function writeFeatureFile(filePath, frontmatter, content) {
88
- const frontmatterText = yaml.stringify(frontmatter);
89
- const fileContent = `---\n${frontmatterText}---\n${content}`;
90
-
91
- // 백업 생성
92
- try {
93
- const backupPath = `${filePath}.backup`;
94
- await fs.copyFile(filePath, backupPath);
95
- } catch (error) {
96
- // 백업 실패는 무시
97
- }
98
-
99
- await fs.writeFile(filePath, fileContent, 'utf8');
100
- }
101
-
102
- /**
103
- * Git 커밋 정보를 Feature 파일에 추가
104
- * @param {string} featureId - Feature ID
105
- * @param {Array<Object>} commits - 커밋 정보 배열
106
- * @returns {Promise<boolean>} - 성공 여부
107
- */
108
- async function updateFeatureWithCommits(featureId, commits) {
109
- try {
110
- const featureFile = await findFeatureFile(featureId);
111
- if (!featureFile) {
112
- console.error(`Feature file not found for ${featureId}`);
113
- return false;
114
- }
115
-
116
- const { frontmatter, content } = await readFeatureFile(featureFile.path);
117
-
118
- // Frontmatter 업데이트
119
- if (!frontmatter.git_commits) {
120
- frontmatter.git_commits = [];
121
- }
122
-
123
- // 커밋 객체로 저장 (사양에 맞게)
124
- const existingHashes = new Set(frontmatter.git_commits.map(c =>
125
- typeof c === 'string' ? c : c.hash
126
- ));
127
-
128
- commits.forEach(commit => {
129
- if (!existingHashes.has(commit.hash)) {
130
- // 사양에 정의된 구조로 커밋 정보 저장
131
- frontmatter.git_commits.push({
132
- hash: commit.hash,
133
- message: commit.message,
134
- author: commit.author,
135
- date: commit.date,
136
- files_changed: 0, // 추후 git 명령어로 업데이트 가능
137
- insertions: 0, // 추후 git 명령어로 업데이트 가능
138
- deletions: 0 // 추후 git 명령어로 업데이트 가능
139
- });
140
- }
141
- });
142
-
143
- // 마지막 업데이트 시간 갱신
144
- frontmatter.last_updated = new Date().toISOString().replace('T', ' ').substring(0, 16);
145
-
146
- // Git History 섹션 업데이트
147
- let updatedContent = content;
148
- const gitHistorySection = '## Git History';
149
- const gitHistoryIndex = content.indexOf(gitHistorySection);
150
-
151
- if (gitHistoryIndex === -1) {
152
- // Git History 섹션이 없으면 추가
153
- updatedContent += '\n\n## Git History\n### Commits\n';
154
- }
155
-
156
- // 커밋 정보 추가
157
- const commitLines = commits.map(commit => {
158
- const date = new Date(commit.date).toISOString().substring(0, 10);
159
- return `- \`${commit.hash.substring(0, 7)}\`: ${commit.message.split('\n')[0]} [${date}]`;
160
- });
161
-
162
- if (gitHistoryIndex !== -1) {
163
- // 기존 섹션에 추가
164
- const sections = updatedContent.split('##');
165
- const gitHistorySectionIndex = sections.findIndex(s => s.trim().startsWith('Git History'));
166
-
167
- if (gitHistorySectionIndex !== -1) {
168
- const commitsSectionIndex = sections[gitHistorySectionIndex].indexOf('### Commits');
169
- if (commitsSectionIndex !== -1) {
170
- sections[gitHistorySectionIndex] += '\n' + commitLines.join('\n');
171
- }
172
- updatedContent = sections.join('##');
173
- }
174
- } else {
175
- updatedContent += commitLines.join('\n');
176
- }
177
-
178
- await writeFeatureFile(featureFile.path, frontmatter, updatedContent);
179
- return true;
180
- } catch (error) {
181
- console.error(`Error updating feature ${featureId}:`, error);
182
- return false;
183
- }
184
- }
185
-
186
- /**
187
- * Feature와 Git 커밋 동기화
188
- * @param {string} featureId - Feature ID
189
- * @returns {Promise<{success: boolean, commitCount: number}>}
190
- */
191
- async function syncFeatureWithGit(featureId) {
192
- try {
193
- const commits = await getCommitsByFeatureId(featureId);
194
-
195
- if (commits.length === 0) {
196
- return { success: true, commitCount: 0 };
197
- }
198
-
199
- const success = await updateFeatureWithCommits(featureId, commits);
200
- return { success, commitCount: commits.length };
201
- } catch (error) {
202
- console.error(`Error syncing feature ${featureId}:`, error);
203
- return { success: false, commitCount: 0 };
204
- }
205
- }
206
-
207
- /**
208
- * 모든 활성 Feature를 Git과 동기화
209
- * @returns {Promise<{total: number, synced: number, failed: number}>}
210
- */
211
- async function syncAllActiveFeatures() {
212
- const basePath = path.join(process.cwd(), '.aiwf', '06_FEATURE_LEDGERS', 'active');
213
- const results = { total: 0, synced: 0, failed: 0 };
214
-
215
- try {
216
- const files = await fs.readdir(basePath);
217
- const featureFiles = files.filter(f => f.endsWith('.md'));
218
- results.total = featureFiles.length;
219
-
220
- for (const file of featureFiles) {
221
- const featureId = file.split('_')[0];
222
- const { success } = await syncFeatureWithGit(featureId);
223
-
224
- if (success) {
225
- results.synced++;
226
- } else {
227
- results.failed++;
228
- }
229
- }
230
- } catch (error) {
231
- console.error('Error syncing all features:', error);
232
- }
233
-
234
- return results;
235
- }
236
-
237
- /**
238
- * Feature 진행률 업데이트
239
- * @param {string} featureId - Feature ID
240
- * @param {number} percentage - 진행률 (0-100)
241
- * @returns {Promise<boolean>} - 성공 여부
242
- */
243
- async function updateFeatureProgress(featureId, percentage) {
244
- try {
245
- const featureFile = await findFeatureFile(featureId);
246
- if (!featureFile) {
247
- return false;
248
- }
249
-
250
- const { frontmatter, content } = await readFeatureFile(featureFile.path);
251
-
252
- frontmatter.progress_percentage = Math.min(100, Math.max(0, percentage));
253
- frontmatter.last_updated = new Date().toISOString().replace('T', ' ').substring(0, 16);
254
-
255
- await writeFeatureFile(featureFile.path, frontmatter, content);
256
- return true;
257
- } catch (error) {
258
- console.error(`Error updating progress for ${featureId}:`, error);
259
- return false;
260
- }
261
- }
262
-
263
- module.exports = {
264
- findFeatureFile,
265
- readFeatureFile,
266
- writeFeatureFile,
267
- updateFeatureWithCommits,
268
- syncFeatureWithGit,
269
- syncAllActiveFeatures,
270
- updateFeatureProgress
271
- };
@@ -1,173 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { promises as fs } from 'fs';
4
- import path from 'path';
5
- import { exec, execSync } from 'child_process';
6
- import { promisify } from 'util';
7
- import { fileURLToPath } from 'url';
8
-
9
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
- const execAsync = promisify(exec);
11
-
12
- // AIWF 경로 설정
13
- const AIWF_ROOT = path.join(process.cwd(), '.aiwf');
14
- const FEATURE_LEDGERS_PATH = path.join(AIWF_ROOT, '06_FEATURE_LEDGERS');
15
- const ACTIVE_PATH = path.join(FEATURE_LEDGERS_PATH, 'active');
16
-
17
- // 커밋 메시지에서 Feature ID를 추출하는 정규식 패턴들
18
- const FEATURE_ID_PATTERNS = [
19
- /FL\d{3}(?!\d)/, // FL001 (not followed by digit)
20
- /\[FL\d{3}\]/, // [FL001]
21
- /\(FL\d{3}\)/, // (FL001)
22
- /(feat|fix|docs|style|refactor|test|chore)\(FL\d{3}\)/, // feat(FL001)
23
- /FL\d{3}:/, // FL001:
24
- /#FL\d{3}/, // #FL001
25
- /relates to FL\d{3}/i, // relates to FL001
26
- ];
27
-
28
- // Feature ID 추출 함수
29
- function extractFeatureId(text) {
30
- for (const pattern of FEATURE_ID_PATTERNS) {
31
- const match = text.match(pattern);
32
- if (match) {
33
- // 순수 Feature ID만 추출 (FL###)
34
- const idMatch = match[0].match(/FL\d{3}/);
35
- if (idMatch) {
36
- return idMatch[0];
37
- }
38
- }
39
- }
40
- return null;
41
- }
42
-
43
- // Feature 파일 경로 가져오기
44
- async function getFeaturePath(featureId) {
45
- // active 폴더에서 먼저 확인
46
- const activePath = path.join(ACTIVE_PATH, `${featureId}_*.md`);
47
- try {
48
- const files = await fs.readdir(ACTIVE_PATH);
49
- const featureFile = files.find(file => file.startsWith(`${featureId}_`));
50
- if (featureFile) {
51
- return path.join(ACTIVE_PATH, featureFile);
52
- }
53
- } catch (error) {
54
- // 폴더가 없거나 읽기 실패 시 계속 진행
55
- }
56
-
57
- // completed, archived 폴더도 확인
58
- const otherPaths = ['completed', 'archived'];
59
- for (const folder of otherPaths) {
60
- const folderPath = path.join(FEATURE_LEDGERS_PATH, folder);
61
- try {
62
- const files = await fs.readdir(folderPath);
63
- const featureFile = files.find(file => file.startsWith(`${featureId}_`));
64
- if (featureFile) {
65
- return path.join(folderPath, featureFile);
66
- }
67
- } catch (error) {
68
- // 폴더가 없거나 읽기 실패 시 계속 진행
69
- }
70
- }
71
-
72
- return null;
73
- }
74
-
75
- // Git 커밋 정보 가져오기
76
- async function getCommitInfo(commitHash) {
77
- try {
78
- const { stdout } = await execAsync(`git show --no-patch --format="%H|%h|%an|%ae|%ad|%s" ${commitHash}`);
79
- const [hash, shortHash, author, email, date, message] = stdout.trim().split('|');
80
- return {
81
- hash,
82
- shortHash,
83
- author,
84
- email,
85
- date: new Date(date).toISOString(),
86
- message
87
- };
88
- } catch (error) {
89
- console.error(`Error getting commit info for ${commitHash}:`, error.message);
90
- return null;
91
- }
92
- }
93
-
94
- // YAML frontmatter 파싱
95
- function parseYamlFrontmatter(content) {
96
- const match = content.match(/^---\n([\s\S]*?)\n---/);
97
- if (!match) return null;
98
-
99
- const yaml = match[1];
100
- const metadata = {};
101
-
102
- // 간단한 YAML 파싱 (복잡한 구조는 제외)
103
- yaml.split('\n').forEach(line => {
104
- const keyValue = line.match(/^(\w+):\s*(.*)$/);
105
- if (keyValue) {
106
- const [_, key, value] = keyValue;
107
- metadata[key] = value.trim();
108
- }
109
- });
110
-
111
- return metadata;
112
- }
113
-
114
- // YAML frontmatter 업데이트
115
- function updateYamlFrontmatter(content, metadata) {
116
- const existingMatch = content.match(/^---\n([\s\S]*?)\n---/);
117
-
118
- let yamlLines = [];
119
- Object.entries(metadata).forEach(([key, value]) => {
120
- if (value !== undefined && value !== null) {
121
- yamlLines.push(`${key}: ${value}`);
122
- }
123
- });
124
-
125
- const newYaml = `---\n${yamlLines.join('\n')}\n---`;
126
-
127
- if (existingMatch) {
128
- return content.replace(/^---\n[\s\S]*?\n---/, newYaml);
129
- } else {
130
- return `${newYaml}\n\n${content}`;
131
- }
132
- }
133
-
134
- // Git 로그에서 Feature 관련 커밋 가져오기
135
- async function getFeatureCommits(featureId, since = null) {
136
- try {
137
- let gitCommand = `git log --grep="${featureId}" --format="%H|%h|%an|%ad|%s"`;
138
- if (since) {
139
- gitCommand += ` --since="${since}"`;
140
- }
141
-
142
- const { stdout } = await execAsync(gitCommand);
143
- if (!stdout.trim()) return [];
144
-
145
- const commits = stdout.trim().split('\n').map(line => {
146
- const [hash, shortHash, author, date, message] = line.split('|');
147
- return {
148
- hash,
149
- shortHash,
150
- author,
151
- date: new Date(date).toISOString(),
152
- message
153
- };
154
- });
155
-
156
- return commits;
157
- } catch (error) {
158
- console.error(`Error getting commits for ${featureId}:`, error.message);
159
- return [];
160
- }
161
- }
162
-
163
- export {
164
- extractFeatureId,
165
- getFeaturePath,
166
- getCommitInfo,
167
- parseYamlFrontmatter,
168
- updateYamlFrontmatter,
169
- getFeatureCommits,
170
- AIWF_ROOT,
171
- FEATURE_LEDGERS_PATH,
172
- ACTIVE_PATH
173
- };