aiwf 0.3.18 → 0.3.19

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 (33) hide show
  1. package/README.ko.md +84 -0
  2. package/README.md +87 -0
  3. package/ai-tools/README.md +105 -0
  4. package/ai-tools/augment/README.md +371 -0
  5. package/ai-tools/augment/config.json +30 -0
  6. package/ai-tools/augment/template/.augment/aiwf-integration.md +387 -0
  7. package/ai-tools/augment/template/.augment/augment.yaml +347 -0
  8. package/ai-tools/augment/template/augment.config.json +64 -0
  9. package/ai-tools/claude-code/README.md +151 -0
  10. package/ai-tools/claude-code/config.json +28 -0
  11. package/ai-tools/claude-code/template/CLAUDE.md +91 -0
  12. package/ai-tools/cursor/README.md +314 -0
  13. package/ai-tools/cursor/config.json +29 -0
  14. package/ai-tools/cursor/template/.cursorrules +362 -0
  15. package/ai-tools/github-copilot/README.md +195 -0
  16. package/ai-tools/github-copilot/config.json +24 -0
  17. package/ai-tools/github-copilot/template/.github/copilot-instructions.md +202 -0
  18. package/ai-tools/windsurf/README.md +355 -0
  19. package/ai-tools/windsurf/config.json +29 -0
  20. package/ai-tools/windsurf/template/.windsurf/aiwf-rules.md +260 -0
  21. package/ai-tools/windsurf/template/.windsurf/windsurf.config.js +312 -0
  22. package/ai-tools/windsurf/template/windsurf.config.json +63 -0
  23. package/docs/CODE_CLEANUP_GUIDE.ko.md +415 -0
  24. package/docs/CODE_CLEANUP_GUIDE.md +415 -0
  25. package/docs/VALIDATOR_API.ko.md +324 -0
  26. package/docs/VALIDATOR_API.md +324 -0
  27. package/package.json +3 -2
  28. package/src/cli/index.js +338 -0
  29. package/src/lib/backup-manager.js +4 -3
  30. package/src/lib/installer.js +126 -3
  31. package/src/lib/validator.js +78 -313
  32. package/src/utils/messages.js +16 -2
  33. package/src/utils/paths.js +1 -20
package/src/cli/index.js CHANGED
@@ -555,6 +555,69 @@ checkpoint
555
555
  }
556
556
  });
557
557
 
558
+ checkpoint
559
+ .command('add [type]')
560
+ .alias('create')
561
+ .description('Create a checkpoint / 체크포인트 생성')
562
+ .option('-m, --message <text>', 'Description for the checkpoint / 체크포인트 설명')
563
+ .option('--meta <json>', 'Additional metadata as JSON / 추가 메타데이터(JSON)')
564
+ .option('--cleanup <n>', 'Keep only last N checkpoints after creating / 생성 후 최근 N개만 보존')
565
+ .action(async (type, options) => {
566
+ try {
567
+ const fs = await import('fs/promises');
568
+ const path = await import('path');
569
+ let currentDir = process.cwd();
570
+
571
+ // 프로젝트 루트 탐색 (.aiwf 기준)
572
+ while (currentDir !== path.parse(currentDir).root) {
573
+ try {
574
+ await fs.access(path.join(currentDir, '.aiwf'));
575
+ break;
576
+ } catch {
577
+ currentDir = path.dirname(currentDir);
578
+ }
579
+ }
580
+
581
+ const manager = new CheckpointManager(currentDir);
582
+ await manager.initialize();
583
+
584
+ // 메타데이터 구성
585
+ const metadata = {};
586
+ if (options.message) metadata.message = options.message;
587
+ if (options.meta) {
588
+ try {
589
+ const parsed = JSON.parse(options.meta);
590
+ Object.assign(metadata, parsed);
591
+ } catch (e) {
592
+ console.error(chalk.red('❌ --meta 값이 올바른 JSON 형식이 아닙니다.'));
593
+ process.exit(1);
594
+ }
595
+ }
596
+
597
+ const cpType = type || 'manual';
598
+ const checkpointId = await manager.createCheckpoint(cpType, metadata);
599
+
600
+ console.log(chalk.green('✅ 체크포인트 생성 완료!'));
601
+ console.log(` ID: ${chalk.cyan(checkpointId)} 유형: ${chalk.yellow(cpType)}`);
602
+ if (metadata.message) {
603
+ console.log(` 메시지: ${chalk.blue(metadata.message)}`);
604
+ }
605
+
606
+ if (options.cleanup) {
607
+ const keep = parseInt(options.cleanup, 10);
608
+ if (!Number.isNaN(keep) && keep > 0) {
609
+ await manager.cleanup(keep);
610
+ console.log(`🧹 최근 ${chalk.cyan(keep)}개만 보존하도록 정리했습니다.`);
611
+ } else {
612
+ console.log(chalk.yellow('⚠️ --cleanup 값이 유효하지 않아 정리를 건너뜁니다. 정수로 입력하세요.'));
613
+ }
614
+ }
615
+ } catch (error) {
616
+ console.error(chalk.red('❌ 체크포인트 생성 실패:'), error.message);
617
+ process.exit(1);
618
+ }
619
+ });
620
+
558
621
  checkpoint
559
622
  .command('restore <checkpointId>')
560
623
  .description('Restore from checkpoint / 체크포인트에서 복구')
@@ -632,6 +695,281 @@ checkpoint
632
695
  }
633
696
  });
634
697
 
698
+ // 체크포인트 리포트 생성
699
+ checkpoint
700
+ .command('report')
701
+ .description('Generate progress report / 진행 상황 리포트 생성')
702
+ .action(async () => {
703
+ try {
704
+ const fs = await import('fs/promises');
705
+ const path = await import('path');
706
+ let currentDir = process.cwd();
707
+
708
+ while (currentDir !== path.parse(currentDir).root) {
709
+ try {
710
+ await fs.access(path.join(currentDir, '.aiwf'));
711
+ break;
712
+ } catch {
713
+ currentDir = path.dirname(currentDir);
714
+ }
715
+ }
716
+
717
+ const manager = new CheckpointManager(currentDir);
718
+ await manager.loadState();
719
+
720
+ console.log(chalk.blue('📊 진행 상황 리포트 생성 중...'));
721
+ console.log('');
722
+
723
+ const report = await manager.generateProgressReport();
724
+
725
+ console.log(chalk.bold('🎯 YOLO 세션 리포트'));
726
+ console.log(''.padEnd(50, '='));
727
+ console.log('');
728
+
729
+ // 세션 정보
730
+ console.log(chalk.bold('📋 세션 정보:'));
731
+ console.log(` ID: ${chalk.cyan(report.session.id)}`);
732
+ console.log(` 시작: ${chalk.gray(new Date(report.session.started).toLocaleString())}`);
733
+ console.log(` 스프린트: ${chalk.blue(report.session.sprint || 'N/A')}`);
734
+ console.log('');
735
+
736
+ // 진행 상황
737
+ console.log(chalk.bold('📈 진행 상황:'));
738
+ console.log(` 완료: ${chalk.green(report.progress.completed)}개`);
739
+ console.log(` 실패: ${chalk.red(report.progress.failed)}개`);
740
+ console.log(` 건너뜀: ${chalk.yellow(report.progress.skipped)}개`);
741
+ console.log('');
742
+
743
+ // 성능 지표
744
+ console.log(chalk.bold('⏱️ 성능 지표:'));
745
+ console.log(` 총 시간: ${chalk.blue(report.performance.total_time)}`);
746
+ console.log(` 평균 태스크 시간: ${chalk.blue(report.performance.avg_task_time)}`);
747
+ console.log(` 성공률: ${chalk.green(report.performance.success_rate)}`);
748
+
749
+ } catch (error) {
750
+ console.error(chalk.red('❌ 리포트 생성 실패:'), error.message);
751
+ process.exit(1);
752
+ }
753
+ });
754
+
755
+ // 체크포인트 정리
756
+ checkpoint
757
+ .command('clean')
758
+ .description('Clean old checkpoints / 오래된 체크포인트 정리')
759
+ .option('--keep <n>', 'Number of checkpoints to keep / 유지할 체크포인트 수', '10')
760
+ .option('--dry-run', 'Preview without deletion / 실제 삭제 없이 미리보기')
761
+ .action(async (options) => {
762
+ try {
763
+ const fs = await import('fs/promises');
764
+ const path = await import('path');
765
+ let currentDir = process.cwd();
766
+
767
+ while (currentDir !== path.parse(currentDir).root) {
768
+ try {
769
+ await fs.access(path.join(currentDir, '.aiwf'));
770
+ break;
771
+ } catch {
772
+ currentDir = path.dirname(currentDir);
773
+ }
774
+ }
775
+
776
+ const manager = new CheckpointManager(currentDir);
777
+ const keepLast = parseInt(options.keep);
778
+
779
+ console.log(chalk.blue(`🧹 체크포인트 정리 중... (최근 ${keepLast}개 유지)`));
780
+
781
+ if (options.dryRun) {
782
+ console.log(chalk.yellow('🔍 드라이런 모드: 실제 삭제는 수행하지 않습니다.'));
783
+ const checkpoints = await manager.listCheckpoints();
784
+ if (checkpoints.length <= keepLast) {
785
+ console.log(chalk.green('✅ 정리할 체크포인트가 없습니다.'));
786
+ } else {
787
+ const toDelete = checkpoints.slice(keepLast);
788
+ console.log(chalk.yellow(`삭제 예정 체크포인트 (${toDelete.length}개):`));
789
+ for (const cp of toDelete) {
790
+ console.log(` ${chalk.red('🗑️')} ${chalk.cyan(cp.id)}`);
791
+ }
792
+ }
793
+ } else {
794
+ await manager.cleanup(keepLast);
795
+ console.log(chalk.green('✅ 체크포인트 정리 완료!'));
796
+ }
797
+
798
+ } catch (error) {
799
+ console.error(chalk.red('❌ 체크포인트 정리 실패:'), error.message);
800
+ process.exit(1);
801
+ }
802
+ });
803
+
804
+ // Engineering Guard commands (오버엔지니어링 방지)
805
+ const guard = program.command('guard');
806
+ guard.description('Engineering guard for overengineering prevention / 오버엔지니어링 방지 가드');
807
+
808
+ guard
809
+ .command('check [path]')
810
+ .description('Check project complexity / 프로젝트 복잡도 검사')
811
+ .option('--config <path>', 'YOLO config file path / YOLO 설정 파일 경로', '.aiwf/yolo-config.yaml')
812
+ .action(async (targetPath, options) => {
813
+ try {
814
+ const { EngineeringGuard } = await import('../utils/engineering-guard.js');
815
+ const guard = new EngineeringGuard();
816
+
817
+ // 설정 파일 로드
818
+ await guard.loadConfig(options.config);
819
+
820
+ const checkPath = targetPath || process.cwd();
821
+ console.log(chalk.blue(`🔍 복잡도 검사 중: ${checkPath}`));
822
+ console.log('');
823
+
824
+ const report = await guard.checkProject(checkPath);
825
+
826
+ // 결과 출력
827
+ console.log(chalk.bold('📊 복잡도 분석 결과:'));
828
+ console.log(` 총 위반사항: ${chalk.yellow(report.summary.total_violations)}`);
829
+ console.log(` 높은 심각도: ${chalk.red(report.summary.high_severity)}`);
830
+ console.log(` 중간 심각도: ${chalk.yellow(report.summary.medium_severity)}`);
831
+ console.log(` 경고사항: ${chalk.blue(report.summary.warnings)}`);
832
+ console.log('');
833
+
834
+ if (report.violations && report.violations.length > 0) {
835
+ console.log(chalk.bold('🚨 위반사항:'));
836
+ for (const violation of report.violations.slice(0, 10)) {
837
+ const icon = violation.severity === 'high' ? '❌' :
838
+ violation.severity === 'medium' ? '⚠️' : '💡';
839
+ console.log(` ${icon} ${chalk.cyan(violation.type)}: ${violation.file || violation.message}`);
840
+ if (violation.details) {
841
+ console.log(` ${chalk.gray(violation.details)}`);
842
+ }
843
+ }
844
+ console.log('');
845
+ }
846
+
847
+ if (report.recommendations && report.recommendations.length > 0) {
848
+ console.log(chalk.bold('💡 권장사항:'));
849
+ for (const rec of report.recommendations) {
850
+ console.log(` • ${chalk.green(rec)}`);
851
+ }
852
+ console.log('');
853
+ }
854
+
855
+ // 통과/실패 판정
856
+ if (report.passed) {
857
+ console.log(chalk.green('✅ 복잡도 기준 통과'));
858
+ } else {
859
+ console.log(chalk.red('❌ 복잡도 기준 초과 - 리팩토링 필요'));
860
+ }
861
+
862
+ } catch (error) {
863
+ console.error(chalk.red('❌ 복잡도 검사 실패:'), error.message);
864
+ process.exit(1);
865
+ }
866
+ });
867
+
868
+ guard
869
+ .command('quick [path]')
870
+ .description('Quick complexity check / 빠른 복잡도 체크')
871
+ .action(async (targetPath) => {
872
+ try {
873
+ const { quickCheck } = await import('../utils/engineering-guard.js');
874
+
875
+ const checkPath = targetPath || process.cwd();
876
+ console.log(chalk.blue(`⚡ 빠른 복잡도 체크: ${checkPath}`));
877
+
878
+ const report = await quickCheck(checkPath);
879
+
880
+ if (report.summary.high_severity > 0) {
881
+ console.log(chalk.red('⚠️ 오버엔지니어링 위험 감지!'));
882
+ console.log('권장사항:', report.recommendations.join(', '));
883
+ } else {
884
+ console.log(chalk.green('✅ 복잡도 양호'));
885
+ }
886
+
887
+ } catch (error) {
888
+ console.error(chalk.red('❌ 빠른 체크 실패:'), error.message);
889
+ process.exit(1);
890
+ }
891
+ });
892
+
893
+ guard
894
+ .command('init')
895
+ .description('Initialize YOLO config with guard settings / YOLO 설정 초기화')
896
+ .action(async () => {
897
+ try {
898
+ const fs = await import('fs/promises');
899
+ const path = await import('path');
900
+ const yaml = await import('js-yaml');
901
+
902
+ const configPath = path.join(process.cwd(), '.aiwf', 'yolo-config.yaml');
903
+
904
+ // 기본 설정 생성
905
+ const defaultConfig = {
906
+ engineering_level: 'minimal',
907
+ focus_rules: ['requirement_first', 'simple_solution', 'no_gold_plating', 'stay_on_track'],
908
+ overengineering_prevention: {
909
+ max_file_lines: 300,
910
+ max_function_lines: 50,
911
+ max_nesting_depth: 4,
912
+ max_abstraction_layers: 3,
913
+ limit_design_patterns: true,
914
+ no_future_proofing: true,
915
+ enforce_yagni: true
916
+ }
917
+ };
918
+
919
+ // .aiwf 디렉토리 생성
920
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
921
+
922
+ // 설정 파일 쓰기
923
+ await fs.writeFile(configPath, yaml.dump(defaultConfig), 'utf-8');
924
+
925
+ console.log(chalk.green('✅ YOLO 설정 파일 생성 완료!'));
926
+ console.log(` 경로: ${chalk.cyan(configPath)}`);
927
+ console.log('');
928
+ console.log(chalk.bold('🛡️ 오버엔지니어링 방지 설정:'));
929
+ console.log(` 파일 크기 제한: ${chalk.yellow('300')}줄`);
930
+ console.log(` 함수 크기 제한: ${chalk.yellow('50')}줄`);
931
+ console.log(` 중첩 깊이 제한: ${chalk.yellow('4')}레벨`);
932
+ console.log(` YAGNI 원칙: ${chalk.green('적용')}`);
933
+
934
+ } catch (error) {
935
+ console.error(chalk.red('❌ 설정 초기화 실패:'), error.message);
936
+ process.exit(1);
937
+ }
938
+ });
939
+
940
+ guard
941
+ .command('feedback [area]')
942
+ .description('Get engineering feedback for area / 특정 영역 피드백 받기')
943
+ .action(async (area) => {
944
+ try {
945
+ const { EngineeringGuard } = await import('../utils/engineering-guard.js');
946
+ const guard = new EngineeringGuard();
947
+
948
+ // 설정 파일 로드 시도
949
+ await guard.loadConfig('.aiwf/yolo-config.yaml');
950
+
951
+ const targetArea = area || 'current_task_area';
952
+ console.log(chalk.blue(`💡 엔지니어링 피드백 제공: ${targetArea}`));
953
+ console.log('');
954
+
955
+ const feedback = await guard.provideFeedback(targetArea);
956
+
957
+ if (feedback && feedback.length > 0) {
958
+ for (const item of feedback) {
959
+ const icon = item.level === 'error' ? '❌' :
960
+ item.level === 'warning' ? '⚠️' : '💡';
961
+ console.log(`${icon} ${item.message}`);
962
+ }
963
+ } else {
964
+ console.log(chalk.green('✅ 현재 영역은 복잡도 기준을 만족합니다.'));
965
+ }
966
+
967
+ } catch (error) {
968
+ console.error(chalk.red('❌ 피드백 생성 실패:'), error.message);
969
+ process.exit(1);
970
+ }
971
+ });
972
+
635
973
  // GitHub integration commands
636
974
  const github = program.command('github');
637
975
  github.description('GitHub integration / GitHub 통합');
@@ -5,8 +5,7 @@ import chalk from 'chalk';
5
5
  import {
6
6
  TOOL_DIRS,
7
7
  AIWF_DIRS,
8
- FILES,
9
- getBackupDirName as getBackupDirNamePath
8
+ FILES
10
9
  } from '../utils/paths.js';
11
10
 
12
11
  let BACKUP_DIR = null;
@@ -16,7 +15,9 @@ let BACKUP_DIR = null;
16
15
  * @returns {string} Backup directory name
17
16
  */
18
17
  function getBackupDirName() {
19
- return getBackupDirNamePath();
18
+ const now = new Date();
19
+ const timestamp = now.toISOString().replace(/[:.]/g, '-').slice(0, -5);
20
+ return `backup-${timestamp}`;
20
21
  }
21
22
 
22
23
  /**
@@ -34,7 +34,6 @@ import {
34
34
  import {
35
35
  FILES,
36
36
  TOOL_DIRS,
37
- TEMP_DIRS,
38
37
  AIWF_DIRS,
39
38
  BASE_DIRS,
40
39
  getAllAiwfDirectories,
@@ -507,14 +506,112 @@ async function updateCommands(languagePath, spinner, msg, debugLog) {
507
506
  }
508
507
  }
509
508
 
509
+ /**
510
+ * Check if tool directories exist and handle them
511
+ * @param {Object} spinner - Ora spinner instance
512
+ * @param {Object} msg - Localized messages
513
+ * @param {boolean} debugLog - Debug mode flag
514
+ * @returns {Promise<Object>} Directory status info
515
+ */
516
+ async function checkAndHandleExistingToolDirs(spinner, msg, debugLog) {
517
+ const toolDirStatus = {
518
+ cursor: {
519
+ exists: false,
520
+ hadFiles: false,
521
+ backedUp: false
522
+ },
523
+ windsurf: {
524
+ exists: false,
525
+ hadFiles: false,
526
+ backedUp: false
527
+ }
528
+ };
529
+
530
+ // Check Cursor directory
531
+ try {
532
+ await fs.access(TOOL_DIRS.CURSOR_RULES);
533
+ toolDirStatus.cursor.exists = true;
534
+
535
+ const cursorFiles = await fs.readdir(TOOL_DIRS.CURSOR_RULES);
536
+ const mdcFiles = cursorFiles.filter(file => file.endsWith('.mdc'));
537
+
538
+ if (mdcFiles.length > 0) {
539
+ toolDirStatus.cursor.hadFiles = true;
540
+ logWithSpinner(spinner, `${msg.foundExistingCursor} (${mdcFiles.length} files)`, debugLog);
541
+
542
+ // Backup existing files
543
+ logWithSpinner(spinner, msg.backingUpToolFiles, debugLog);
544
+ const backupDir = path.join(TOOL_DIRS.CURSOR_RULES, `backup_${Date.now()}`);
545
+ await fs.mkdir(backupDir, { recursive: true });
546
+
547
+ for (const file of mdcFiles) {
548
+ const srcPath = path.join(TOOL_DIRS.CURSOR_RULES, file);
549
+ const backupPath = path.join(backupDir, file);
550
+ await fs.copyFile(srcPath, backupPath);
551
+ }
552
+
553
+ toolDirStatus.cursor.backedUp = true;
554
+ logWithSpinner(spinner, `${msg.toolBackupCreated}: ${backupDir}`, debugLog);
555
+
556
+ // Remove existing files
557
+ for (const file of mdcFiles) {
558
+ await fs.unlink(path.join(TOOL_DIRS.CURSOR_RULES, file));
559
+ }
560
+ }
561
+ } catch (error) {
562
+ // Directory doesn't exist, that's fine
563
+ logWithSpinner(spinner, `Cursor rules directory not found, will create new`, debugLog);
564
+ }
565
+
566
+ // Check Windsurf directory
567
+ try {
568
+ await fs.access(TOOL_DIRS.WINDSURF_RULES);
569
+ toolDirStatus.windsurf.exists = true;
570
+
571
+ const windsurfFiles = await fs.readdir(TOOL_DIRS.WINDSURF_RULES);
572
+ const mdFiles = windsurfFiles.filter(file => file.endsWith('.md'));
573
+
574
+ if (mdFiles.length > 0) {
575
+ toolDirStatus.windsurf.hadFiles = true;
576
+ logWithSpinner(spinner, `${msg.foundExistingWindsurf} (${mdFiles.length} files)`, debugLog);
577
+
578
+ // Backup existing files
579
+ logWithSpinner(spinner, msg.backingUpToolFiles, debugLog);
580
+ const backupDir = path.join(TOOL_DIRS.WINDSURF_RULES, `backup_${Date.now()}`);
581
+ await fs.mkdir(backupDir, { recursive: true });
582
+
583
+ for (const file of mdFiles) {
584
+ const srcPath = path.join(TOOL_DIRS.WINDSURF_RULES, file);
585
+ const backupPath = path.join(backupDir, file);
586
+ await fs.copyFile(srcPath, backupPath);
587
+ }
588
+
589
+ toolDirStatus.windsurf.backedUp = true;
590
+ logWithSpinner(spinner, `${msg.toolBackupCreated}: ${backupDir}`, debugLog);
591
+
592
+ // Remove existing files
593
+ for (const file of mdFiles) {
594
+ await fs.unlink(path.join(TOOL_DIRS.WINDSURF_RULES, file));
595
+ }
596
+ }
597
+ } catch (error) {
598
+ // Directory doesn't exist, that's fine
599
+ logWithSpinner(spinner, `Windsurf rules directory not found, will create new`, debugLog);
600
+ }
601
+
602
+ return toolDirStatus;
603
+ }
604
+
510
605
  /**
511
606
  * Download and process rules
512
607
  * @param {Object} spinner - Ora spinner instance
513
- {{ ... }}
514
608
  * @param {Object} msg - Localized messages
515
609
  * @param {boolean} debugLog - Debug mode flag
516
610
  */
517
611
  async function downloadAndProcessRules(spinner, msg, debugLog) {
612
+ // Check and handle existing tool directories first
613
+ logWithSpinner(spinner, msg.checkingExistingTools, debugLog);
614
+ const toolDirStatus = await checkAndHandleExistingToolDirs(spinner, msg, debugLog);
518
615
  // Try to download from GitHub first, fallback to local files
519
616
  const tmpRulesGlobal = path.join(os.tmpdir(), 'aiwf-rules-global');
520
617
  const tmpRulesManual = path.join(os.tmpdir(), 'aiwf-rules-manual');
@@ -670,6 +767,32 @@ async function downloadAndProcessRules(spinner, msg, debugLog) {
670
767
  } catch (error) {
671
768
  logWithSpinner(spinner, `No rules/manual directory found`, debugLog);
672
769
  }
770
+
771
+ // Success message for tool rules installation
772
+ logWithSpinner(spinner, msg.toolRulesInstalled, debugLog);
773
+
774
+ // Display installation results for tool directories
775
+ if (toolDirStatus.cursor.backedUp || toolDirStatus.windsurf.backedUp) {
776
+ console.log(chalk.cyan(`\n📂 Tool Directory Installation Results:`));
777
+
778
+ if (toolDirStatus.cursor.backedUp) {
779
+ console.log(chalk.yellow(` • Cursor: Existing rules backed up and replaced`));
780
+ } else if (toolDirStatus.cursor.exists) {
781
+ console.log(chalk.green(` • Cursor: Rules installed (directory was empty)`));
782
+ } else {
783
+ console.log(chalk.green(` • Cursor: Rules installed (new directory)`));
784
+ }
785
+
786
+ if (toolDirStatus.windsurf.backedUp) {
787
+ console.log(chalk.yellow(` • Windsurf: Existing rules backed up and replaced`));
788
+ } else if (toolDirStatus.windsurf.exists) {
789
+ console.log(chalk.green(` • Windsurf: Rules installed (directory was empty)`));
790
+ } else {
791
+ console.log(chalk.green(` • Windsurf: Rules installed (new directory)`));
792
+ }
793
+ }
794
+
795
+ return toolDirStatus;
673
796
  }
674
797
 
675
798
  /**
@@ -819,7 +942,7 @@ export async function installAIWF(options = {}) {
819
942
  await updateCommands(languagePath, spinner, msg, debugLog);
820
943
 
821
944
  // Download and process rules
822
- await downloadAndProcessRules(spinner, msg, debugLog);
945
+ const toolDirStatus = await downloadAndProcessRules(spinner, msg, debugLog);
823
946
 
824
947
  // Restore project files if needed
825
948
  if (projectBackupDir) {