aiwf 0.3.18 → 0.3.20
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/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 -4
- package/src/cli/index.js +134 -335
- 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/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) {
|
package/src/lib/validator.js
CHANGED
|
@@ -3,322 +3,17 @@ import path from 'path';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { TOOL_DIRS, AIWF_DIRS, FILES } from '../utils/paths.js';
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
'aiwf_create_sprint_tasks.md',
|
|
15
|
-
'aiwf_code_review.md'
|
|
16
|
-
],
|
|
17
|
-
minFileCount: 5,
|
|
18
|
-
description: 'Claude Code Commands'
|
|
19
|
-
},
|
|
20
|
-
geminiCLI: {
|
|
21
|
-
baseDir: TOOL_DIRS.GEMINI_PROMPTS,
|
|
22
|
-
requiredFiles: ['README.md'],
|
|
23
|
-
minFileCount: 1,
|
|
24
|
-
description: 'Gemini-CLI Prompts'
|
|
25
|
-
},
|
|
26
|
-
cursor: {
|
|
27
|
-
baseDir: TOOL_DIRS.CURSOR_RULES,
|
|
28
|
-
requiredFiles: [],
|
|
29
|
-
minFileCount: 2,
|
|
30
|
-
fileExtension: '.mdc',
|
|
31
|
-
description: 'Cursor IDE Rules'
|
|
32
|
-
},
|
|
33
|
-
windsurf: {
|
|
34
|
-
baseDir: TOOL_DIRS.WINDSURF_RULES,
|
|
35
|
-
requiredFiles: [],
|
|
36
|
-
minFileCount: 2,
|
|
37
|
-
fileExtension: '.md',
|
|
38
|
-
description: 'Windsurf IDE Rules'
|
|
39
|
-
},
|
|
40
|
-
aiwf: {
|
|
41
|
-
baseDir: AIWF_DIRS.ROOT,
|
|
42
|
-
requiredFiles: ['CLAUDE.md', '00_PROJECT_MANIFEST.md'],
|
|
43
|
-
minFileCount: 2,
|
|
44
|
-
description: 'AIWF Core Structure'
|
|
6
|
+
// Validation constants
|
|
7
|
+
const VALIDATION_CONSTANTS = {
|
|
8
|
+
MIN_FILE_SIZE: 10,
|
|
9
|
+
MIN_RULE_FILE_SIZE: 50,
|
|
10
|
+
MIN_FILE_COUNT: {
|
|
11
|
+
CURSOR_MDC: 2,
|
|
12
|
+
WINDSURF_MD: 2,
|
|
13
|
+
CLAUDE_COMMANDS: 4
|
|
45
14
|
}
|
|
46
15
|
};
|
|
47
16
|
|
|
48
|
-
/**
|
|
49
|
-
* Validate tool installation based on configuration
|
|
50
|
-
* @param {Object} config - Tool configuration
|
|
51
|
-
* @returns {Promise<Object>} Validation results
|
|
52
|
-
*/
|
|
53
|
-
async function validateToolInstallation(config) {
|
|
54
|
-
const results = {
|
|
55
|
-
success: true,
|
|
56
|
-
description: config.description,
|
|
57
|
-
baseDir: config.baseDir,
|
|
58
|
-
checks: {
|
|
59
|
-
directoryExists: false,
|
|
60
|
-
fileCount: 0,
|
|
61
|
-
requiredFiles: [],
|
|
62
|
-
totalSize: 0,
|
|
63
|
-
issues: []
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
// 1. Check directory exists
|
|
69
|
-
await fs.access(config.baseDir);
|
|
70
|
-
const dirStat = await fs.stat(config.baseDir);
|
|
71
|
-
if (dirStat.isDirectory()) {
|
|
72
|
-
results.checks.directoryExists = true;
|
|
73
|
-
} else {
|
|
74
|
-
results.checks.issues.push(`${config.baseDir} is not a directory`);
|
|
75
|
-
results.success = false;
|
|
76
|
-
return results;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// 2. Get file list
|
|
80
|
-
const actualFiles = await fs.readdir(config.baseDir);
|
|
81
|
-
results.checks.fileCount = actualFiles.length;
|
|
82
|
-
|
|
83
|
-
// 3. Check required files
|
|
84
|
-
for (const requiredFile of config.requiredFiles || []) {
|
|
85
|
-
const filePath = path.join(config.baseDir, requiredFile);
|
|
86
|
-
const fileCheck = {
|
|
87
|
-
file: requiredFile,
|
|
88
|
-
exists: false,
|
|
89
|
-
size: 0,
|
|
90
|
-
valid: false
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
if (actualFiles.includes(requiredFile)) {
|
|
94
|
-
fileCheck.exists = true;
|
|
95
|
-
try {
|
|
96
|
-
const stats = await fs.stat(filePath);
|
|
97
|
-
fileCheck.size = stats.size;
|
|
98
|
-
results.checks.totalSize += stats.size;
|
|
99
|
-
|
|
100
|
-
// Validate file size (minimum 10 bytes)
|
|
101
|
-
if (stats.size < 10) {
|
|
102
|
-
results.checks.issues.push(
|
|
103
|
-
`File ${requiredFile} is too small (${stats.size} bytes)`
|
|
104
|
-
);
|
|
105
|
-
results.success = false;
|
|
106
|
-
} else {
|
|
107
|
-
fileCheck.valid = true;
|
|
108
|
-
}
|
|
109
|
-
} catch (error) {
|
|
110
|
-
results.checks.issues.push(
|
|
111
|
-
`Cannot read stats for ${requiredFile}: ${error.message}`
|
|
112
|
-
);
|
|
113
|
-
results.success = false;
|
|
114
|
-
}
|
|
115
|
-
} else {
|
|
116
|
-
results.checks.issues.push(`Required file ${requiredFile} is missing`);
|
|
117
|
-
results.success = false;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
results.checks.requiredFiles.push(fileCheck);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// 4. File extension validation (Cursor/Windsurf)
|
|
124
|
-
if (config.fileExtension) {
|
|
125
|
-
const matchingFiles = actualFiles.filter(f => f.endsWith(config.fileExtension));
|
|
126
|
-
if (matchingFiles.length < config.minFileCount) {
|
|
127
|
-
results.checks.issues.push(
|
|
128
|
-
`Expected at least ${config.minFileCount} ${config.fileExtension} files, found ${matchingFiles.length}`
|
|
129
|
-
);
|
|
130
|
-
results.success = false;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Validate file sizes for extension files
|
|
134
|
-
for (const file of matchingFiles) {
|
|
135
|
-
const filePath = path.join(config.baseDir, file);
|
|
136
|
-
try {
|
|
137
|
-
const stats = await fs.stat(filePath);
|
|
138
|
-
results.checks.totalSize += stats.size;
|
|
139
|
-
|
|
140
|
-
if (stats.size < 50) {
|
|
141
|
-
// Rules files should be at least 50 bytes
|
|
142
|
-
results.checks.issues.push(
|
|
143
|
-
`Rules file ${file} is too small (${stats.size} bytes)`
|
|
144
|
-
);
|
|
145
|
-
results.success = false;
|
|
146
|
-
}
|
|
147
|
-
} catch (error) {
|
|
148
|
-
results.checks.issues.push(`Cannot read ${file}: ${error.message}`);
|
|
149
|
-
results.success = false;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// 5. Minimum file count validation
|
|
155
|
-
if (results.checks.fileCount < config.minFileCount) {
|
|
156
|
-
results.checks.issues.push(
|
|
157
|
-
`Expected at least ${config.minFileCount} files, found ${results.checks.fileCount}`
|
|
158
|
-
);
|
|
159
|
-
results.success = false;
|
|
160
|
-
}
|
|
161
|
-
} catch (error) {
|
|
162
|
-
results.success = false;
|
|
163
|
-
results.checks.issues.push(`Validation error: ${error.message}`);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return results;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Validate Claude Code installation
|
|
171
|
-
* @returns {Promise<Object>} Validation results
|
|
172
|
-
*/
|
|
173
|
-
export async function validateClaudeInstall() {
|
|
174
|
-
return await validateToolInstallation(ESSENTIAL_FILES.claudeCode);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Validate Gemini CLI installation
|
|
179
|
-
* @returns {Promise<Object>} Validation results
|
|
180
|
-
*/
|
|
181
|
-
export async function validateGeminiInstall() {
|
|
182
|
-
return await validateToolInstallation(ESSENTIAL_FILES.geminiCLI);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Validate Cursor installation
|
|
187
|
-
* @returns {Promise<Object>} Validation results
|
|
188
|
-
*/
|
|
189
|
-
export async function validateCursorInstall() {
|
|
190
|
-
return await validateToolInstallation(ESSENTIAL_FILES.cursor);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Validate Windsurf installation
|
|
195
|
-
* @returns {Promise<Object>} Validation results
|
|
196
|
-
*/
|
|
197
|
-
export async function validateWindsurfInstall() {
|
|
198
|
-
return await validateToolInstallation(ESSENTIAL_FILES.windsurf);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Validate AIWF Core installation
|
|
203
|
-
* @returns {Promise<Object>} Validation results
|
|
204
|
-
*/
|
|
205
|
-
export async function validateAIWFCore() {
|
|
206
|
-
return await validateToolInstallation(ESSENTIAL_FILES.aiwf);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Validate installation with detailed results
|
|
211
|
-
* @returns {Promise<Object>} Detailed validation results
|
|
212
|
-
*/
|
|
213
|
-
export async function validateInstallationDetailed() {
|
|
214
|
-
const validationResults = {
|
|
215
|
-
overall: {
|
|
216
|
-
success: true,
|
|
217
|
-
toolsChecked: 0,
|
|
218
|
-
toolsPassed: 0,
|
|
219
|
-
toolsFailed: 0,
|
|
220
|
-
totalIssues: 0
|
|
221
|
-
},
|
|
222
|
-
tools: {}
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
// Always validate AIWF core
|
|
226
|
-
const aiwfResult = await validateAIWFCore();
|
|
227
|
-
validationResults.tools.aiwf = aiwfResult;
|
|
228
|
-
validationResults.overall.toolsChecked++;
|
|
229
|
-
|
|
230
|
-
if (aiwfResult.success) {
|
|
231
|
-
validationResults.overall.toolsPassed++;
|
|
232
|
-
} else {
|
|
233
|
-
validationResults.overall.toolsFailed++;
|
|
234
|
-
validationResults.overall.success = false;
|
|
235
|
-
validationResults.overall.totalIssues += aiwfResult.checks.issues.length;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Validate all tools
|
|
239
|
-
const toolValidators = {
|
|
240
|
-
claudeCode: validateClaudeInstall,
|
|
241
|
-
geminiCLI: validateGeminiInstall,
|
|
242
|
-
cursor: validateCursorInstall,
|
|
243
|
-
windsurf: validateWindsurfInstall
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
for (const [toolName, validator] of Object.entries(toolValidators)) {
|
|
247
|
-
const result = await validator();
|
|
248
|
-
validationResults.tools[toolName] = result;
|
|
249
|
-
validationResults.overall.toolsChecked++;
|
|
250
|
-
|
|
251
|
-
if (result.success) {
|
|
252
|
-
validationResults.overall.toolsPassed++;
|
|
253
|
-
} else {
|
|
254
|
-
validationResults.overall.toolsFailed++;
|
|
255
|
-
validationResults.overall.success = false;
|
|
256
|
-
validationResults.overall.totalIssues += result.checks.issues.length;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return validationResults;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Enhanced installation validation with tool selection
|
|
265
|
-
* @param {Array|null} selectedTools - Tools to validate
|
|
266
|
-
* @param {string} language - Language code
|
|
267
|
-
* @returns {Promise<Object>} Validation results
|
|
268
|
-
*/
|
|
269
|
-
export async function validateInstallationEnhanced(selectedTools = null, language = 'en') {
|
|
270
|
-
const validationResults = {
|
|
271
|
-
overall: {
|
|
272
|
-
success: true,
|
|
273
|
-
toolsChecked: 0,
|
|
274
|
-
toolsPassed: 0,
|
|
275
|
-
toolsFailed: 0,
|
|
276
|
-
totalIssues: 0
|
|
277
|
-
},
|
|
278
|
-
tools: {}
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
// Always validate AIWF core
|
|
282
|
-
const aiwfResult = await validateAIWFCore();
|
|
283
|
-
validationResults.tools.aiwf = aiwfResult;
|
|
284
|
-
validationResults.overall.toolsChecked++;
|
|
285
|
-
|
|
286
|
-
if (aiwfResult.success) {
|
|
287
|
-
validationResults.overall.toolsPassed++;
|
|
288
|
-
} else {
|
|
289
|
-
validationResults.overall.toolsFailed++;
|
|
290
|
-
validationResults.overall.success = false;
|
|
291
|
-
validationResults.overall.totalIssues += aiwfResult.checks.issues.length;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Determine tools to validate
|
|
295
|
-
const toolsToValidate = selectedTools || ['claudeCode', 'geminiCLI', 'cursor', 'windsurf'];
|
|
296
|
-
|
|
297
|
-
const toolValidators = {
|
|
298
|
-
claudeCode: validateClaudeInstall,
|
|
299
|
-
geminiCLI: validateGeminiInstall,
|
|
300
|
-
cursor: validateCursorInstall,
|
|
301
|
-
windsurf: validateWindsurfInstall
|
|
302
|
-
};
|
|
303
|
-
|
|
304
|
-
for (const toolName of toolsToValidate) {
|
|
305
|
-
if (toolValidators[toolName]) {
|
|
306
|
-
const result = await toolValidators[toolName]();
|
|
307
|
-
validationResults.tools[toolName] = result;
|
|
308
|
-
validationResults.overall.toolsChecked++;
|
|
309
|
-
|
|
310
|
-
if (result.success) {
|
|
311
|
-
validationResults.overall.toolsPassed++;
|
|
312
|
-
} else {
|
|
313
|
-
validationResults.overall.toolsFailed++;
|
|
314
|
-
validationResults.overall.success = false;
|
|
315
|
-
validationResults.overall.totalIssues += result.checks.issues.length;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return validationResults;
|
|
321
|
-
}
|
|
322
17
|
|
|
323
18
|
/**
|
|
324
19
|
* Simplified validation with clean interface
|
|
@@ -440,6 +135,16 @@ async function validateClaudeCode() {
|
|
|
440
135
|
if (!exists) {
|
|
441
136
|
return { success: false, reason: `Missing file: ${filePath}` };
|
|
442
137
|
}
|
|
138
|
+
|
|
139
|
+
// Check file size
|
|
140
|
+
try {
|
|
141
|
+
const stats = await fs.stat(filePath);
|
|
142
|
+
if (stats.size < VALIDATION_CONSTANTS.MIN_FILE_SIZE) {
|
|
143
|
+
return { success: false, reason: `File ${fileName} is too small (${stats.size} bytes)` };
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
return { success: false, reason: `Cannot read file ${fileName}: ${error.message}` };
|
|
147
|
+
}
|
|
443
148
|
}
|
|
444
149
|
|
|
445
150
|
return { success: true };
|
|
@@ -485,6 +190,36 @@ async function validateCursorTool() {
|
|
|
485
190
|
return { success: false, reason: `Missing Cursor rules directory: ${TOOL_DIRS.CURSOR_RULES}` };
|
|
486
191
|
}
|
|
487
192
|
|
|
193
|
+
// Enhanced validation: Check file count and types
|
|
194
|
+
const files = await fs.readdir(TOOL_DIRS.CURSOR_RULES);
|
|
195
|
+
const mdcFiles = files.filter(file => file.endsWith('.mdc'));
|
|
196
|
+
|
|
197
|
+
if (mdcFiles.length < VALIDATION_CONSTANTS.MIN_FILE_COUNT.CURSOR_MDC) {
|
|
198
|
+
return {
|
|
199
|
+
success: false,
|
|
200
|
+
reason: `Cursor rules: Expected at least ${VALIDATION_CONSTANTS.MIN_FILE_COUNT.CURSOR_MDC} .mdc files, found ${mdcFiles.length}`
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Validate file sizes
|
|
205
|
+
for (const file of mdcFiles) {
|
|
206
|
+
const filePath = path.join(TOOL_DIRS.CURSOR_RULES, file);
|
|
207
|
+
try {
|
|
208
|
+
const stats = await fs.stat(filePath);
|
|
209
|
+
if (stats.size < VALIDATION_CONSTANTS.MIN_RULE_FILE_SIZE) {
|
|
210
|
+
return {
|
|
211
|
+
success: false,
|
|
212
|
+
reason: `Cursor rules file ${file} is too small (${stats.size} bytes, minimum ${VALIDATION_CONSTANTS.MIN_RULE_FILE_SIZE})`
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
} catch (error) {
|
|
216
|
+
return {
|
|
217
|
+
success: false,
|
|
218
|
+
reason: `Cannot read Cursor rules file ${file}: ${error.message}`
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
488
223
|
return { success: true };
|
|
489
224
|
} catch (error) {
|
|
490
225
|
return { success: false, reason: `Cursor validation error: ${error.message}` };
|
|
@@ -505,6 +240,36 @@ async function validateWindsurfTool() {
|
|
|
505
240
|
return { success: false, reason: `Missing Windsurf rules directory: ${TOOL_DIRS.WINDSURF_RULES}` };
|
|
506
241
|
}
|
|
507
242
|
|
|
243
|
+
// Enhanced validation: Check file count and types
|
|
244
|
+
const files = await fs.readdir(TOOL_DIRS.WINDSURF_RULES);
|
|
245
|
+
const mdFiles = files.filter(file => file.endsWith('.md'));
|
|
246
|
+
|
|
247
|
+
if (mdFiles.length < VALIDATION_CONSTANTS.MIN_FILE_COUNT.WINDSURF_MD) {
|
|
248
|
+
return {
|
|
249
|
+
success: false,
|
|
250
|
+
reason: `Windsurf rules: Expected at least ${VALIDATION_CONSTANTS.MIN_FILE_COUNT.WINDSURF_MD} .md files, found ${mdFiles.length}`
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Validate file sizes
|
|
255
|
+
for (const file of mdFiles) {
|
|
256
|
+
const filePath = path.join(TOOL_DIRS.WINDSURF_RULES, file);
|
|
257
|
+
try {
|
|
258
|
+
const stats = await fs.stat(filePath);
|
|
259
|
+
if (stats.size < VALIDATION_CONSTANTS.MIN_RULE_FILE_SIZE) {
|
|
260
|
+
return {
|
|
261
|
+
success: false,
|
|
262
|
+
reason: `Windsurf rules file ${file} is too small (${stats.size} bytes, minimum ${VALIDATION_CONSTANTS.MIN_RULE_FILE_SIZE})`
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
} catch (error) {
|
|
266
|
+
return {
|
|
267
|
+
success: false,
|
|
268
|
+
reason: `Cannot read Windsurf rules file ${file}: ${error.message}`
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
508
273
|
return { success: true };
|
|
509
274
|
} catch (error) {
|
|
510
275
|
return { success: false, reason: `Windsurf validation error: ${error.message}` };
|
package/src/utils/messages.js
CHANGED
|
@@ -69,7 +69,14 @@ export const messages = {
|
|
|
69
69
|
checkingInstallation: 'AIWF 설치 상태를 확인하는 중...',
|
|
70
70
|
creatingDirectories: 'AIWF 디렉토리 구조를 생성하는 중...',
|
|
71
71
|
restoringBackup: '백업에서 파일을 복원하는 중...',
|
|
72
|
-
backupRestored: '{count}개 파일이 복원되었습니다'
|
|
72
|
+
backupRestored: '{count}개 파일이 복원되었습니다',
|
|
73
|
+
checkingExistingTools: '기존 도구 디렉토리를 확인하는 중...',
|
|
74
|
+
foundExistingCursor: 'Cursor 규칙 파일이 발견되었습니다',
|
|
75
|
+
foundExistingWindsurf: 'Windsurf 규칙 파일이 발견되었습니다',
|
|
76
|
+
backingUpToolFiles: '기존 도구 파일을 백업하는 중...',
|
|
77
|
+
toolBackupCreated: '도구 파일 백업이 생성되었습니다',
|
|
78
|
+
installingToolRules: '도구 규칙을 설치하는 중...',
|
|
79
|
+
toolRulesInstalled: '도구 규칙이 성공적으로 설치되었습니다',
|
|
73
80
|
},
|
|
74
81
|
en: {
|
|
75
82
|
welcome: '\n🎉 Welcome to AIWF!\n',
|
|
@@ -138,7 +145,14 @@ export const messages = {
|
|
|
138
145
|
checkingInstallation: 'Checking AIWF installation status...',
|
|
139
146
|
creatingDirectories: 'Creating AIWF directory structure...',
|
|
140
147
|
restoringBackup: 'Restoring files from backup...',
|
|
141
|
-
backupRestored: '{count} files restored'
|
|
148
|
+
backupRestored: '{count} files restored',
|
|
149
|
+
checkingExistingTools: 'Checking existing tool directories...',
|
|
150
|
+
foundExistingCursor: 'Found existing Cursor rules',
|
|
151
|
+
foundExistingWindsurf: 'Found existing Windsurf rules',
|
|
152
|
+
backingUpToolFiles: 'Backing up existing tool files...',
|
|
153
|
+
toolBackupCreated: 'Tool files backup created',
|
|
154
|
+
installingToolRules: 'Installing tool rules...',
|
|
155
|
+
toolRulesInstalled: 'Tool rules installed successfully',
|
|
142
156
|
}
|
|
143
157
|
};
|
|
144
158
|
|
package/src/utils/paths.js
CHANGED
|
@@ -56,11 +56,6 @@ export const FILES = {
|
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
// Temporary directories
|
|
60
|
-
export const TEMP_DIRS = {
|
|
61
|
-
RULES_GLOBAL: path.join(AIWF_DIRS.ROOT, '_tmp_rules_global'),
|
|
62
|
-
RULES_MANUAL: path.join(AIWF_DIRS.ROOT, '_tmp_rules_manual')
|
|
63
|
-
};
|
|
64
59
|
|
|
65
60
|
// User cache directory (for global cache)
|
|
66
61
|
export const USER_CACHE_DIR = path.join(
|
|
@@ -103,18 +98,6 @@ export function getToolDirectory(tool) {
|
|
|
103
98
|
return mapping[tool] || null;
|
|
104
99
|
}
|
|
105
100
|
|
|
106
|
-
// Get backup directory name with timestamp
|
|
107
|
-
export function getBackupDirName() {
|
|
108
|
-
const now = new Date();
|
|
109
|
-
const pad = n => n.toString().padStart(2, '0');
|
|
110
|
-
const y = now.getFullYear();
|
|
111
|
-
const m = pad(now.getMonth() + 1);
|
|
112
|
-
const d = pad(now.getDate());
|
|
113
|
-
const h = pad(now.getHours());
|
|
114
|
-
const min = pad(now.getMinutes());
|
|
115
|
-
const s = pad(now.getSeconds());
|
|
116
|
-
return path.join(AIWF_DIRS.ROOT, `backup_${y}${m}${d}_${h}${min}${s}`);
|
|
117
|
-
}
|
|
118
101
|
|
|
119
102
|
// Export all for convenience
|
|
120
103
|
export default {
|
|
@@ -122,10 +105,8 @@ export default {
|
|
|
122
105
|
AIWF_DIRS,
|
|
123
106
|
TOOL_DIRS,
|
|
124
107
|
FILES,
|
|
125
|
-
TEMP_DIRS,
|
|
126
108
|
USER_CACHE_DIR,
|
|
127
109
|
getAllAiwfDirectories,
|
|
128
110
|
getAllClaudeMdFiles,
|
|
129
|
-
getToolDirectory
|
|
130
|
-
getBackupDirName
|
|
111
|
+
getToolDirectory
|
|
131
112
|
};
|