aiwf 0.3.13 → 0.3.15

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.
@@ -52,6 +52,61 @@ async function checkExistingInstallation() {
52
52
  return flagExists;
53
53
  }
54
54
 
55
+ /**
56
+ * Check if existing project files exist
57
+ * @returns {Promise<{hasProject: boolean, projectFiles: string[]}>} Project status
58
+ */
59
+ async function checkExistingProject() {
60
+ const projectFiles = [];
61
+ let hasProject = false;
62
+
63
+ // Check for key project files
64
+ const keyFiles = [
65
+ FILES.PROJECT_MANIFEST,
66
+ path.join(AIWF_DIRS.PROJECT_DOCS, 'README.md'),
67
+ path.join(AIWF_DIRS.REQUIREMENTS, 'requirements.md'),
68
+ path.join(AIWF_DIRS.SPRINTS, 'sprint-01.md'),
69
+ path.join(AIWF_DIRS.GENERAL_TASKS, 'tasks.md'),
70
+ path.join(AIWF_DIRS.ARCHITECTURAL_DECISIONS, 'decisions.md'),
71
+ path.join(AIWF_DIRS.STATE_OF_PROJECT, 'current-state.md')
72
+ ];
73
+
74
+ for (const file of keyFiles) {
75
+ try {
76
+ await fs.access(file);
77
+ projectFiles.push(file);
78
+ hasProject = true;
79
+ } catch (error) {
80
+ // File doesn't exist, continue
81
+ }
82
+ }
83
+
84
+ // Check for any .md files in project directories
85
+ const projectDirs = [
86
+ AIWF_DIRS.PROJECT_DOCS,
87
+ AIWF_DIRS.REQUIREMENTS,
88
+ AIWF_DIRS.SPRINTS,
89
+ AIWF_DIRS.GENERAL_TASKS,
90
+ AIWF_DIRS.ARCHITECTURAL_DECISIONS,
91
+ AIWF_DIRS.STATE_OF_PROJECT
92
+ ];
93
+
94
+ for (const dir of projectDirs) {
95
+ try {
96
+ const files = await fs.readdir(dir);
97
+ const mdFiles = files.filter(file => file.endsWith('.md'));
98
+ if (mdFiles.length > 0) {
99
+ hasProject = true;
100
+ projectFiles.push(...mdFiles.map(file => path.join(dir, file)));
101
+ }
102
+ } catch (error) {
103
+ // Directory doesn't exist, continue
104
+ }
105
+ }
106
+
107
+ return { hasProject, projectFiles };
108
+ }
109
+
55
110
  /**
56
111
  * Log with spinner for debug mode
57
112
  * @param {Object} spinner - Ora spinner instance
@@ -146,34 +201,145 @@ async function selectLanguage(options) {
146
201
  * Handle existing installation
147
202
  * @param {Object} msg - Localized messages
148
203
  * @param {Object} options - Installation options
149
- * @returns {Promise<boolean>} Continue with installation
204
+ * @returns {Promise<{continue: boolean, installType: string, preserveProject: boolean}>} Installation decision
150
205
  */
151
206
  async function handleExistingInstallation(msg, options) {
207
+
152
208
  if (options.force) {
153
- return true;
209
+ return { continue: true, installType: 'reinstall', preserveProject: false };
154
210
  }
155
211
 
156
- const response = await prompts({
212
+ // First, ask about installation type
213
+ const installResponse = await prompts({
157
214
  type: 'select',
158
215
  name: 'action',
159
216
  message: msg.existingDetected,
160
217
  choices: [
161
218
  { title: msg.updateOption, value: 'update' },
219
+ { title: msg.reinstallOption, value: 'reinstall' },
162
220
  { title: msg.skipOption, value: 'skip' },
163
221
  { title: msg.cancelOption, value: 'cancel' }
164
222
  ]
165
223
  });
166
224
 
167
- if (response.action === 'skip' || response.action === 'cancel') {
225
+
226
+
227
+ if (installResponse.action === 'skip' || installResponse.action === 'cancel') {
168
228
  console.log(chalk.yellow(msg.installCancelled));
169
229
  process.exit(0);
170
230
  }
171
231
 
172
- if (response.action === 'update') {
232
+ let preserveProject = true; // Default to preserve
233
+
234
+ // If reinstalling, don't preserve project (complete fresh install)
235
+ if (installResponse.action === 'reinstall') {
236
+ preserveProject = false;
237
+ }
238
+
239
+ // Backup if updating
240
+ if (installResponse.action === 'update') {
173
241
  await backupCommandsAndDocs(msg);
174
242
  }
175
243
 
176
- return true;
244
+ return {
245
+ continue: true,
246
+ installType: installResponse.action,
247
+ preserveProject
248
+ };
249
+ }
250
+
251
+ /**
252
+ * Backup project files
253
+ * @param {string[]} projectFiles - List of project files to backup
254
+ * @param {Object} msg - Localized messages
255
+ * @returns {Promise<string>} Backup directory path
256
+ */
257
+ async function backupProjectFiles(projectFiles, msg) {
258
+ if (projectFiles.length === 0) {
259
+ return null;
260
+ }
261
+
262
+ const backupDir = path.join(os.tmpdir(), `aiwf-project-backup-${Date.now()}`);
263
+ await fs.mkdir(backupDir, { recursive: true });
264
+
265
+ console.log(chalk.cyan('💾 Backing up project files...'));
266
+
267
+ for (const file of projectFiles) {
268
+ try {
269
+ const relativePath = path.relative(process.cwd(), file);
270
+ const backupPath = path.join(backupDir, relativePath);
271
+ const backupDirPath = path.dirname(backupPath);
272
+
273
+ await fs.mkdir(backupDirPath, { recursive: true });
274
+ await fs.copyFile(file, backupPath);
275
+ } catch (error) {
276
+ console.log(chalk.yellow(`Warning: Could not backup ${file}: ${error.message}`));
277
+ }
278
+ }
279
+
280
+ console.log(chalk.green(`✅ Project files backed up to: ${backupDir}`));
281
+ return backupDir;
282
+ }
283
+
284
+ /**
285
+ * Restore project files from backup
286
+ * @param {string} backupDir - Backup directory path
287
+ * @param {Object} msg - Localized messages
288
+ */
289
+ async function restoreProjectFiles(backupDir, msg) {
290
+ if (!backupDir) {
291
+ return;
292
+ }
293
+
294
+ console.log(chalk.cyan('🔄 Restoring project files...'));
295
+
296
+ try {
297
+ const backupFiles = await fs.readdir(backupDir, { recursive: true });
298
+
299
+ for (const file of backupFiles) {
300
+ const backupFilePath = path.join(backupDir, file);
301
+ const stat = await fs.stat(backupFilePath);
302
+
303
+ if (stat.isFile()) {
304
+ const targetPath = path.join(process.cwd(), file);
305
+ const targetDir = path.dirname(targetPath);
306
+
307
+ await fs.mkdir(targetDir, { recursive: true });
308
+ await fs.copyFile(backupFilePath, targetPath);
309
+ }
310
+ }
311
+
312
+ console.log(chalk.green(msg.projectPreserved));
313
+
314
+ // Clean up backup directory
315
+ await fs.rm(backupDir, { recursive: true, force: true });
316
+ } catch (error) {
317
+ console.log(chalk.yellow(`Warning: Could not restore some project files: ${error.message}`));
318
+ console.log(chalk.yellow(`Backup preserved at: ${backupDir}`));
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Remove project files
324
+ * @param {string[]} projectFiles - List of project files to remove
325
+ * @param {Object} msg - Localized messages
326
+ */
327
+ async function removeProjectFiles(projectFiles, msg) {
328
+ if (projectFiles.length === 0) {
329
+ return;
330
+ }
331
+
332
+ console.log(chalk.cyan('🗑️ Removing existing project files...'));
333
+
334
+ for (const file of projectFiles) {
335
+ try {
336
+ await fs.unlink(file);
337
+ } catch (error) {
338
+ // File might already be deleted, continue
339
+ }
340
+ }
341
+
342
+ console.log(chalk.green(msg.projectOverwritten));
177
343
  }
178
344
 
179
345
  /**
@@ -190,6 +356,7 @@ async function createDirectoryStructure() {
190
356
  // Create tool-specific directories
191
357
  await fs.mkdir(TOOL_DIRS.CURSOR_RULES, { recursive: true });
192
358
  await fs.mkdir(TOOL_DIRS.WINDSURF_RULES, { recursive: true });
359
+ await fs.mkdir(TOOL_DIRS.CLAUDE_AGENTS, { recursive: true });
193
360
  }
194
361
 
195
362
  /**
@@ -285,7 +452,7 @@ async function updateDocumentation(languagePath, spinner, msg, debugLog) {
285
452
  }
286
453
 
287
454
  /**
288
- * Update commands
455
+ * Update Claude commands and agents
289
456
  * @param {string} languagePath - Language path
290
457
  * @param {Object} spinner - Ora spinner instance
291
458
  * @param {Object} msg - Localized messages
@@ -298,7 +465,14 @@ async function updateCommands(languagePath, spinner, msg, debugLog) {
298
465
  await fs.rm(TOOL_DIRS.CLAUDE_COMMANDS, { recursive: true, force: true });
299
466
  }
300
467
 
468
+ // Delete existing agents folder if it exists
469
+ if (await fs.access(TOOL_DIRS.CLAUDE_AGENTS).then(() => true).catch(() => false)) {
470
+ logWithSpinner(spinner, 'Deleting old agents...', debugLog);
471
+ await fs.rm(TOOL_DIRS.CLAUDE_AGENTS, { recursive: true, force: true });
472
+ }
473
+
301
474
  await fs.mkdir(TOOL_DIRS.CLAUDE_COMMANDS, { recursive: true });
475
+ await fs.mkdir(TOOL_DIRS.CLAUDE_AGENTS, { recursive: true });
302
476
 
303
477
  // Download commands
304
478
  logWithSpinner(spinner, msg.downloadingCommands, debugLog);
@@ -314,11 +488,28 @@ async function updateCommands(languagePath, spinner, msg, debugLog) {
314
488
  } catch (error) {
315
489
  logWithSpinner(spinner, msg.commandsNotFound, debugLog);
316
490
  }
491
+
492
+ // Download agents
493
+ logWithSpinner(spinner, 'Downloading agents...', debugLog);
494
+ try {
495
+ await downloadDirectory(
496
+ `${GITHUB_CONTENT_PREFIX}/${languagePath}/${TOOL_DIRS.CLAUDE_AGENTS}`,
497
+ TOOL_DIRS.CLAUDE_AGENTS,
498
+ spinner,
499
+ msg,
500
+ languagePath,
501
+ false // agents도 기존 방식 유지 (파일 리스트 사용)
502
+ );
503
+ logWithSpinner(spinner, 'Successfully downloaded agents', debugLog);
504
+ } catch (error) {
505
+ logWithSpinner(spinner, 'Agents not found or failed to download', debugLog);
506
+ }
317
507
  }
318
508
 
319
509
  /**
320
510
  * Download and process rules
321
511
  * @param {Object} spinner - Ora spinner instance
512
+ {{ ... }}
322
513
  * @param {Object} msg - Localized messages
323
514
  * @param {boolean} debugLog - Debug mode flag
324
515
  */
@@ -584,9 +775,22 @@ export async function installAIWF(options = {}) {
584
775
  // Check existing installation
585
776
  const hasExisting = await checkExistingInstallation();
586
777
 
778
+ let installDecision = { continue: true, installType: 'fresh', preserveProject: false };
779
+ let projectBackupDir = null;
780
+
587
781
  if (hasExisting) {
588
- const shouldContinue = await handleExistingInstallation(msg, options);
589
- if (!shouldContinue) return;
782
+
783
+ installDecision = await handleExistingInstallation(msg, options);
784
+
785
+ if (!installDecision.continue) return;
786
+
787
+ // If reinstalling and preserving project, backup project files
788
+ if (installDecision.installType === 'reinstall' && installDecision.preserveProject) {
789
+ const { hasProject, projectFiles } = await checkExistingProject();
790
+ if (hasProject) {
791
+ projectBackupDir = await backupProjectFiles(projectFiles, msg);
792
+ }
793
+ }
590
794
  }
591
795
 
592
796
  const spinner = ora(msg.fetching).start();
@@ -595,16 +799,20 @@ export async function installAIWF(options = {}) {
595
799
  // Create directory structure
596
800
  await createDirectoryStructure();
597
801
 
598
- // Download manifest (fresh installs only)
599
- if (!hasExisting) {
802
+ // Download manifest (fresh installs only or complete reinstall)
803
+ if (!hasExisting || installDecision.installType === 'reinstall') {
600
804
  await downloadManifest(languagePath, spinner, msg, debugLog);
601
805
  }
602
806
 
603
- // Download templates
604
- await downloadTemplates(languagePath, spinner, msg, debugLog);
807
+ // Download templates (fresh installs and complete reinstall only)
808
+ if (!hasExisting || installDecision.installType === 'reinstall') {
809
+ await downloadTemplates(languagePath, spinner, msg, debugLog);
810
+ }
605
811
 
606
- // Update documentation
607
- await updateDocumentation(languagePath, spinner, msg, debugLog);
812
+ // Update documentation (fresh installs and complete reinstall only)
813
+ if (!hasExisting || installDecision.installType === 'reinstall') {
814
+ await updateDocumentation(languagePath, spinner, msg, debugLog);
815
+ }
608
816
 
609
817
  // Update commands
610
818
  await updateCommands(languagePath, spinner, msg, debugLog);
@@ -612,8 +820,13 @@ export async function installAIWF(options = {}) {
612
820
  // Download and process rules
613
821
  await downloadAndProcessRules(spinner, msg, debugLog);
614
822
 
823
+ // Restore project files if needed
824
+ if (projectBackupDir) {
825
+ await restoreProjectFiles(projectBackupDir, msg);
826
+ }
827
+
615
828
  // Success
616
- if (hasExisting) {
829
+ if (hasExisting && installDecision.installType === 'update') {
617
830
  spinner.succeed(chalk.green(msg.updateSuccess));
618
831
  } else {
619
832
  spinner.succeed(chalk.green(msg.installSuccess));
@@ -11,8 +11,16 @@ export const messages = {
11
11
  english: 'English',
12
12
  existingDetected: '기존 AIWF 설치가 감지되었습니다. 무엇을 하시겠습니까?',
13
13
  updateOption: '업데이트 (명령어와 문서만 업데이트하고 작업 내용은 보존)',
14
+ reinstallOption: '완전 재설치 (모든 내용을 새로 설치, 기존 프로젝트 덮어쓰기)',
14
15
  skipOption: '설치 건너뛰기',
15
16
  cancelOption: '취소',
17
+ projectDetected: '기존 프로젝트가 감지되었습니다. 프로젝트 파일을 어떻게 처리하시겠습니까?',
18
+ preserveProject: '프로젝트 보존 (기존 프로젝트 파일 유지)',
19
+ overwriteProject: '덮어쓰기 (새로 설치하고 기존 프로젝트 삭제)',
20
+ checkingProject: '기존 프로젝트 확인 중...',
21
+ projectFound: '기존 프로젝트가 발견되었습니다:',
22
+ projectPreserved: '💾 프로젝트가 보존되었습니다.',
23
+ projectOverwritten: '🔄 프로젝트가 새로 설치되었습니다.',
16
24
  installCancelled: '\n설치가 취소되었습니다.',
17
25
  backingUp: '기존 명령어 및 문서 백업 중...',
18
26
  fetching: 'GitHub에서 AIWF 프레임워크를 가져오는 중...',
@@ -72,8 +80,16 @@ export const messages = {
72
80
  english: 'English',
73
81
  existingDetected: 'Existing AIWF installation detected. What would you like to do?',
74
82
  updateOption: 'Update (Update commands and docs only, preserve work content)',
83
+ reinstallOption: 'Complete reinstall (Install everything fresh, overwrite existing project)',
75
84
  skipOption: 'Skip installation',
76
85
  cancelOption: 'Cancel',
86
+ projectDetected: 'Existing project detected. How would you like to handle project files?',
87
+ preserveProject: 'Preserve project (Keep existing project files)',
88
+ overwriteProject: 'Overwrite (Fresh install and remove existing project)',
89
+ checkingProject: 'Checking for existing project...',
90
+ projectFound: 'Existing project found:',
91
+ projectPreserved: '💾 Project has been preserved.',
92
+ projectOverwritten: '🔄 Project has been freshly installed.',
77
93
  installCancelled: '\nInstallation cancelled.',
78
94
  backingUp: 'Backing up existing commands and documents...',
79
95
  fetching: 'Fetching AIWF framework from GitHub...',
@@ -22,7 +22,7 @@ export const AIWF_DIRS = {
22
22
  REQUIREMENTS: path.join(BASE_DIRS.AIWF, '02_REQUIREMENTS'),
23
23
  SPRINTS: path.join(BASE_DIRS.AIWF, '03_SPRINTS'),
24
24
  GENERAL_TASKS: path.join(BASE_DIRS.AIWF, '04_GENERAL_TASKS'),
25
- ARCHITECTURE_DECISIONS: path.join(BASE_DIRS.AIWF, '05_ARCHITECTURE_DECISIONS'),
25
+ ARCHITECTURAL_DECISIONS: path.join(BASE_DIRS.AIWF, '05_ARCHITECTURAL_DECISIONS'),
26
26
  STATE_OF_PROJECT: path.join(BASE_DIRS.AIWF, '10_STATE_OF_PROJECT'),
27
27
  PROMPTS: path.join(BASE_DIRS.AIWF, '98_PROMPTS'),
28
28
  TEMPLATES: path.join(BASE_DIRS.AIWF, '99_TEMPLATES'),
@@ -37,6 +37,7 @@ export const AIWF_DIRS = {
37
37
  // Tool-specific directories
38
38
  export const TOOL_DIRS = {
39
39
  CLAUDE_COMMANDS: path.join(BASE_DIRS.CLAUDE, 'commands', 'aiwf'),
40
+ CLAUDE_AGENTS: path.join(BASE_DIRS.CLAUDE, 'agents'),
40
41
  GEMINI_PROMPTS: path.join(BASE_DIRS.GEMINI, 'prompts', 'aiwf'),
41
42
  CURSOR_RULES: path.join(BASE_DIRS.CURSOR, 'rules'),
42
43
  WINDSURF_RULES: path.join(BASE_DIRS.WINDSURF, 'rules')
@@ -76,7 +77,7 @@ export function getAllAiwfDirectories() {
76
77
  AIWF_DIRS.REQUIREMENTS,
77
78
  AIWF_DIRS.SPRINTS,
78
79
  AIWF_DIRS.GENERAL_TASKS,
79
- AIWF_DIRS.ARCHITECTURE_DECISIONS,
80
+ AIWF_DIRS.ARCHITECTURAL_DECISIONS,
80
81
  AIWF_DIRS.STATE_OF_PROJECT,
81
82
  AIWF_DIRS.PROMPTS,
82
83
  AIWF_DIRS.TEMPLATES
@@ -1,86 +0,0 @@
1
- # AIWF 프로젝트 정리 권장사항
2
-
3
- ## ✅ 완료된 작업
4
-
5
- 1. **import 경로 수정**
6
- - index.js의 잘못된 경로 수정 완료
7
- - `./src/commands/ai-tool.js` → `./commands/ai-tool.js`
8
-
9
- 2. **미사용 파일 삭제** (4개 파일, 1,734줄 제거)
10
- - lib/file-batch-processor.js
11
- - lib/performance-benchmark.js
12
- - lib/template-manager.js
13
- - lib/cache-integration.js
14
-
15
- ## 📋 추가 권장사항
16
-
17
- ### 1. 코드 구조 개선
18
-
19
- #### index.js 모듈화 (1,950줄 → 여러 모듈로 분리)
20
- ```
21
- index.js
22
- ├── lib/installer.js # 설치 로직
23
- ├── lib/validator.js # 검증 로직
24
- ├── lib/backup-manager.js # 백업/복원 로직
25
- └── lib/github-client.js # GitHub API 통신
26
- ```
27
-
28
- #### 토큰 관리 시스템 통합
29
- 현재 8개 파일에 분산된 토큰 기능을 2개로 통합:
30
- - `lib/token-manager.js` - 핵심 토큰 관리
31
- - `commands/token-tracking.js` - CLI 명령어
32
-
33
- ### 2. 파일명 일관성
34
-
35
- 스네이크 케이스를 케밥 케이스로 통일:
36
- ```bash
37
- # 변경 필요
38
- sync-feature-commits.js → sync-feature-commits.js ✓ (완료)
39
- feature-commit-report.js → feature-commit-report.js ✓ (완료)
40
- scan-git-history.js → scan-git-history.js ✓ (완료)
41
- ```
42
-
43
- ### 3. 중복 기능 통합
44
-
45
- #### 압축 시스템 (4개 → 2개)
46
- - 유지: `utils/context-compressor.js`, `commands/compress-context.js`
47
- - 통합: `compression-strategies.js`, `persona-aware-compressor.js`
48
-
49
- ### 4. 문서 정리
50
-
51
- 중복되거나 오래된 문서 통합:
52
- - PRD.md와 PRD.ko.md의 정보 동기화
53
- - 여러 개의 가이드 문서를 하나로 통합
54
- - 사용되지 않는 문서에서만 참조되는 기능 제거
55
-
56
- ### 5. 테스트 개선
57
-
58
- - 큰 테스트 파일 분리 (compression-test-suite.js: 1,251줄)
59
- - 주석 처리된 테스트 코드 정리
60
- - 실제 사용되는 유틸리티에 대한 테스트 추가
61
-
62
- ### 6. 성능 최적화
63
-
64
- - 복잡한 메트릭 수집 간소화 (metrics-collector.js: 884줄)
65
- - 불필요한 추상화 레벨 제거
66
- - 캐시 시스템 단순화
67
-
68
- ## 🎯 예상 효과
69
-
70
- - **코드 감소**: 약 20-30% 코드량 감소
71
- - **유지보수성**: 파일 구조 명확화로 이해도 향상
72
- - **성능**: 불필요한 추상화 제거로 성능 개선
73
- - **일관성**: 명명 규칙 통일로 개발 경험 개선
74
-
75
- ## ⚠️ 주의사항
76
-
77
- 1. **Git 통합 파일들은 유지**: 실제로 활발히 사용 중
78
- 2. **페르소나 시스템 유지**: Claude Code 통합에 필수
79
- 3. **변경 시 테스트 필수**: 기능 손상 방지
80
-
81
- ## 🔄 단계별 진행
82
-
83
- 1. **Phase 1** (즉시): ✅ 미사용 파일 제거 (완료)
84
- 2. **Phase 2** (단기): 파일명 통일, 중복 통합
85
- 3. **Phase 3** (중기): index.js 모듈화, 테스트 개선
86
- 4. **Phase 4** (장기): 전체 구조 최적화