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.
- package/README.ko.md +84 -0
- package/README.md +87 -0
- package/ai-tools/README.md +105 -0
- package/ai-tools/augment/README.md +371 -0
- package/ai-tools/augment/config.json +30 -0
- package/ai-tools/augment/template/.augment/aiwf-integration.md +387 -0
- package/ai-tools/augment/template/.augment/augment.yaml +347 -0
- package/ai-tools/augment/template/augment.config.json +64 -0
- package/ai-tools/claude-code/README.md +151 -0
- package/ai-tools/claude-code/config.json +28 -0
- package/ai-tools/claude-code/template/CLAUDE.md +91 -0
- package/ai-tools/cursor/README.md +314 -0
- package/ai-tools/cursor/config.json +29 -0
- package/ai-tools/cursor/template/.cursorrules +362 -0
- package/ai-tools/github-copilot/README.md +195 -0
- package/ai-tools/github-copilot/config.json +24 -0
- package/ai-tools/github-copilot/template/.github/copilot-instructions.md +202 -0
- package/ai-tools/windsurf/README.md +355 -0
- package/ai-tools/windsurf/config.json +29 -0
- package/ai-tools/windsurf/template/.windsurf/aiwf-rules.md +260 -0
- package/ai-tools/windsurf/template/.windsurf/windsurf.config.js +312 -0
- package/ai-tools/windsurf/template/windsurf.config.json +63 -0
- package/docs/CODE_CLEANUP_GUIDE.ko.md +415 -0
- package/docs/CODE_CLEANUP_GUIDE.md +415 -0
- package/docs/VALIDATOR_API.ko.md +324 -0
- package/docs/VALIDATOR_API.md +324 -0
- package/package.json +3 -2
- package/src/cli/index.js +338 -0
- package/src/lib/backup-manager.js +4 -3
- package/src/lib/installer.js +126 -3
- package/src/lib/validator.js +78 -313
- package/src/utils/messages.js +16 -2
- 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
|
-
|
|
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
|
/**
|
package/src/lib/installer.js
CHANGED
|
@@ -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) {
|