@agentskillkit/agent-skills 3.2.2 â 3.2.5
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/.agent/skills/mobile-design/scripts/mobile_audit.js +333 -0
- package/.agent/skills/typescript-expert/scripts/ts_diagnostic.js +227 -0
- package/README.md +197 -720
- package/package.json +4 -4
- package/packages/cli/lib/audit.js +2 -2
- package/packages/cli/lib/auto-learn.js +8 -8
- package/packages/cli/lib/eslint-fix.js +1 -1
- package/packages/cli/lib/fix.js +5 -5
- package/packages/cli/lib/hooks/install-hooks.js +4 -4
- package/packages/cli/lib/hooks/lint-learn.js +4 -4
- package/packages/cli/lib/knowledge-index.js +4 -4
- package/packages/cli/lib/knowledge-metrics.js +2 -2
- package/packages/cli/lib/knowledge-retention.js +3 -3
- package/packages/cli/lib/knowledge-validator.js +3 -3
- package/packages/cli/lib/learn.js +10 -10
- package/packages/cli/lib/recall.js +1 -1
- package/packages/cli/lib/skill-learn.js +2 -2
- package/packages/cli/lib/stats.js +3 -3
- package/packages/cli/lib/ui/dashboard-ui.js +222 -0
- package/packages/cli/lib/ui/help-ui.js +41 -18
- package/packages/cli/lib/ui/index.js +57 -5
- package/packages/cli/lib/ui/settings-ui.js +292 -14
- package/packages/cli/lib/ui/stats-ui.js +93 -43
- package/packages/cli/lib/watcher.js +2 -2
- package/packages/kit/kit.js +89 -0
- package/packages/kit/lib/agents.js +208 -0
- package/packages/kit/lib/commands/analyze.js +70 -0
- package/packages/kit/lib/commands/cache.js +65 -0
- package/packages/kit/lib/commands/doctor.js +75 -0
- package/packages/kit/lib/commands/help.js +155 -0
- package/packages/kit/lib/commands/info.js +38 -0
- package/packages/kit/lib/commands/init.js +39 -0
- package/packages/kit/lib/commands/install.js +803 -0
- package/packages/kit/lib/commands/list.js +43 -0
- package/packages/kit/lib/commands/lock.js +57 -0
- package/packages/kit/lib/commands/uninstall.js +307 -0
- package/packages/kit/lib/commands/update.js +55 -0
- package/packages/kit/lib/commands/validate.js +69 -0
- package/packages/kit/lib/commands/verify.js +56 -0
- package/packages/kit/lib/config.js +81 -0
- package/packages/kit/lib/helpers.js +196 -0
- package/packages/kit/lib/helpers.test.js +60 -0
- package/packages/kit/lib/installer.js +164 -0
- package/packages/kit/lib/skills.js +119 -0
- package/packages/kit/lib/skills.test.js +109 -0
- package/packages/kit/lib/types.js +82 -0
- package/packages/kit/lib/ui.js +329 -0
- package/.agent/skills/mobile-design/scripts/mobile_audit.py +0 -670
- package/.agent/skills/requirements-python.txt +0 -25
- package/.agent/skills/requirements.txt +0 -96
- package/.agent/skills/typescript-expert/scripts/ts_diagnostic.py +0 -203
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Mobile UX Audit Script - Full Mobile Design Coverage
|
|
4
|
+
*
|
|
5
|
+
* Analyzes React Native / Flutter code for compliance with:
|
|
6
|
+
* - Touch Psychology (touch targets, spacing, thumb zones)
|
|
7
|
+
* - Mobile Performance (FlatList, memo, callbacks)
|
|
8
|
+
* - Mobile Navigation (tabs, back handling)
|
|
9
|
+
* - Mobile Typography (system fonts, scaling)
|
|
10
|
+
* - Mobile Color System (dark mode, OLED)
|
|
11
|
+
* - Platform iOS/Android specifics
|
|
12
|
+
* - Mobile Backend (secure storage, offline)
|
|
13
|
+
*
|
|
14
|
+
* Total: 50+ mobile-specific checks
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
|
|
20
|
+
class MobileAuditor {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.issues = [];
|
|
23
|
+
this.warnings = [];
|
|
24
|
+
this.passedCount = 0;
|
|
25
|
+
this.filesChecked = 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
auditFile(filepath) {
|
|
29
|
+
let content;
|
|
30
|
+
try {
|
|
31
|
+
content = fs.readFileSync(filepath, 'utf-8');
|
|
32
|
+
} catch {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.filesChecked++;
|
|
37
|
+
const filename = path.basename(filepath);
|
|
38
|
+
|
|
39
|
+
// Detect framework
|
|
40
|
+
const isReactNative = /react-native|@react-navigation|React\.Native/.test(content);
|
|
41
|
+
const isFlutter = /import 'package:flutter|MaterialApp|Widget\.build/.test(content);
|
|
42
|
+
|
|
43
|
+
if (!isReactNative && !isFlutter) {
|
|
44
|
+
return; // Skip non-mobile files
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// --- 1. TOUCH PSYCHOLOGY CHECKS ---
|
|
48
|
+
|
|
49
|
+
// 1.1 Touch Target Size Check
|
|
50
|
+
const smallSizes = content.match(/(?:width|height|size):\s*([0-3]\d)/g) || [];
|
|
51
|
+
for (const match of smallSizes) {
|
|
52
|
+
const size = parseInt(match.match(/\d+/)[0]);
|
|
53
|
+
if (size < 44) {
|
|
54
|
+
this.issues.push(`[Touch Target] ${filename}: Touch target size ${size}px < 44px minimum`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 1.2 Touch Target Spacing Check
|
|
59
|
+
const smallGaps = content.match(/(?:margin|gap):\s*([0-7])\s*(?:px|dp)/g) || [];
|
|
60
|
+
for (const match of smallGaps) {
|
|
61
|
+
const gap = parseInt(match.match(/\d+/)[0]);
|
|
62
|
+
if (gap < 8) {
|
|
63
|
+
this.warnings.push(`[Touch Spacing] ${filename}: Touch spacing ${gap}px < 8px minimum`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 1.3 Thumb Zone Placement
|
|
68
|
+
const primaryButtons = /(?:testID|id):\s*["'](?:.*(?:primary|cta|submit|confirm)[^"']*)["']/i.test(content);
|
|
69
|
+
const hasBottomPlacement = /position:\s*["']?absolute|bottom:\s*\d+|justifyContent:\s*["']?flex-end/.test(content);
|
|
70
|
+
if (primaryButtons && !hasBottomPlacement) {
|
|
71
|
+
this.warnings.push(`[Thumb Zone] ${filename}: Primary CTA may not be in thumb zone (bottom)`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 1.4 Gesture Alternatives
|
|
75
|
+
const hasSwipe = /Swipeable|onSwipe|PanGestureHandler|swipe/.test(content);
|
|
76
|
+
const hasButtons = /Button.*(?:delete|archive|more)|TouchableOpacity|Pressable/.test(content);
|
|
77
|
+
if (hasSwipe && !hasButtons) {
|
|
78
|
+
this.warnings.push(`[Gestures] ${filename}: Swipe gestures without visible button alternatives`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 1.5 Haptic Feedback
|
|
82
|
+
const hasImportantActions = /(?:onPress|onSubmit|delete|remove|confirm|purchase)/.test(content);
|
|
83
|
+
const hasHaptics = /Haptics|Vibration|react-native-haptic-feedback/.test(content);
|
|
84
|
+
if (hasImportantActions && !hasHaptics) {
|
|
85
|
+
this.warnings.push(`[Haptics] ${filename}: Important actions without haptic feedback`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// --- 2. MOBILE PERFORMANCE CHECKS ---
|
|
89
|
+
|
|
90
|
+
// 2.1 CRITICAL: ScrollView vs FlatList
|
|
91
|
+
const hasScrollView = /<ScrollView|ScrollView\./.test(content);
|
|
92
|
+
const hasMapInScrollView = /ScrollView.*\.map\(|ScrollView.*\{.*\.map/.test(content);
|
|
93
|
+
if (hasScrollView && hasMapInScrollView) {
|
|
94
|
+
this.issues.push(`[Performance CRITICAL] ${filename}: ScrollView with .map() - Use FlatList!`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 2.2 React.memo Check
|
|
98
|
+
if (isReactNative) {
|
|
99
|
+
const hasList = /FlatList|FlashList|SectionList/.test(content);
|
|
100
|
+
const hasMemo = /React\.memo|memo\(/.test(content);
|
|
101
|
+
if (hasList && !hasMemo) {
|
|
102
|
+
this.warnings.push(`[Performance] ${filename}: FlatList without React.memo on list items`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 2.3 useCallback Check
|
|
107
|
+
if (isReactNative) {
|
|
108
|
+
const hasFlatList = /FlatList|FlashList/.test(content);
|
|
109
|
+
const hasCallback = /useCallback/.test(content);
|
|
110
|
+
if (hasFlatList && !hasCallback) {
|
|
111
|
+
this.warnings.push(`[Performance] ${filename}: FlatList renderItem without useCallback`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 2.4 keyExtractor Check (CRITICAL)
|
|
116
|
+
if (isReactNative) {
|
|
117
|
+
const hasFlatList = /FlatList/.test(content);
|
|
118
|
+
const hasKeyExtractor = /keyExtractor/.test(content);
|
|
119
|
+
const usesIndexKey = /key=\{.*index.*\}|key:\s*index/.test(content);
|
|
120
|
+
if (hasFlatList && !hasKeyExtractor) {
|
|
121
|
+
this.issues.push(`[Performance CRITICAL] ${filename}: FlatList without keyExtractor`);
|
|
122
|
+
}
|
|
123
|
+
if (usesIndexKey) {
|
|
124
|
+
this.issues.push(`[Performance CRITICAL] ${filename}: Using index as key - use unique ID`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 2.5 useNativeDriver Check
|
|
129
|
+
if (isReactNative) {
|
|
130
|
+
const hasAnimated = /Animated\./.test(content);
|
|
131
|
+
const hasNativeDriverFalse = /useNativeDriver:\s*false/.test(content);
|
|
132
|
+
if (hasAnimated && hasNativeDriverFalse) {
|
|
133
|
+
this.warnings.push(`[Performance] ${filename}: Animation with useNativeDriver: false`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 2.6 Memory Leak Check
|
|
138
|
+
if (isReactNative) {
|
|
139
|
+
const hasEffect = /useEffect/.test(content);
|
|
140
|
+
const hasCleanup = /return\s*\(\)\s*=>|return\s+function/.test(content);
|
|
141
|
+
const hasSubscriptions = /addEventListener|subscribe|\.focus\(\)|\.off\(/.test(content);
|
|
142
|
+
if (hasEffect && hasSubscriptions && !hasCleanup) {
|
|
143
|
+
this.issues.push(`[Memory Leak] ${filename}: useEffect with subscriptions but no cleanup`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 2.7 Console.log Detection
|
|
148
|
+
const consoleLogs = (content.match(/console\.log|console\.warn|console\.error/g) || []).length;
|
|
149
|
+
if (consoleLogs > 5) {
|
|
150
|
+
this.warnings.push(`[Performance] ${filename}: ${consoleLogs} console.log statements`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 2.8 Inline Function Detection
|
|
154
|
+
if (isReactNative) {
|
|
155
|
+
const inlineFunctions = (content.match(/(?:onPress|renderItem):\s*\([^)]*\)\s*=>/g) || []).length;
|
|
156
|
+
if (inlineFunctions > 3) {
|
|
157
|
+
this.warnings.push(`[Performance] ${filename}: ${inlineFunctions} inline arrow functions - use useCallback`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// --- 3. MOBILE NAVIGATION CHECKS ---
|
|
162
|
+
|
|
163
|
+
// 3.1 Tab Bar Max Items
|
|
164
|
+
const tabItems = (content.match(/Tab\.Screen|createBottomTabNavigator/g) || []).length;
|
|
165
|
+
if (tabItems > 5) {
|
|
166
|
+
this.warnings.push(`[Navigation] ${filename}: ${tabItems} tab items (max 5 recommended)`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// --- 4. MOBILE TYPOGRAPHY CHECKS ---
|
|
170
|
+
|
|
171
|
+
// 4.1 Font Size Limits
|
|
172
|
+
const fontSizes = content.match(/fontSize:\s*([\d.]+)/g) || [];
|
|
173
|
+
for (const match of fontSizes) {
|
|
174
|
+
const size = parseFloat(match.match(/[\d.]+/)[0]);
|
|
175
|
+
if (size < 12) {
|
|
176
|
+
this.warnings.push(`[Typography] ${filename}: fontSize ${size}px below 12px minimum`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// --- 5. MOBILE COLOR SYSTEM CHECKS ---
|
|
181
|
+
|
|
182
|
+
// 5.1 Pure Black Avoidance
|
|
183
|
+
if (/#000000|color:\s*black|backgroundColor:\s*["']?black/.test(content)) {
|
|
184
|
+
this.warnings.push(`[Color] ${filename}: Pure black (#000000) - use dark gray for OLED`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 5.2 Dark Mode Support
|
|
188
|
+
const hasColorScheme = /useColorScheme|colorScheme|appearance:\s*["']?dark/.test(content);
|
|
189
|
+
if (!hasColorScheme) {
|
|
190
|
+
this.warnings.push(`[Color] ${filename}: No dark mode support detected`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// --- 6. PLATFORM iOS CHECKS ---
|
|
194
|
+
|
|
195
|
+
if (isReactNative) {
|
|
196
|
+
// 6.1 iOS Safe Area
|
|
197
|
+
const hasSafeArea = /SafeAreaView|useSafeAreaInsets/.test(content);
|
|
198
|
+
if (!hasSafeArea) {
|
|
199
|
+
this.warnings.push(`[iOS] ${filename}: No SafeArea detected - content may be hidden by notch`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// --- 7. PLATFORM ANDROID CHECKS ---
|
|
204
|
+
|
|
205
|
+
if (isReactNative) {
|
|
206
|
+
// 7.1 Ripple Effect
|
|
207
|
+
const hasRipple = /ripple|android_ripple/.test(content);
|
|
208
|
+
const hasPressable = /Pressable|Touchable/.test(content);
|
|
209
|
+
if (hasPressable && !hasRipple) {
|
|
210
|
+
this.warnings.push(`[Android] ${filename}: Touchable without ripple effect`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// --- 8. MOBILE BACKEND CHECKS ---
|
|
215
|
+
|
|
216
|
+
// 8.1 Secure Storage Check
|
|
217
|
+
const hasAsyncStorage = /AsyncStorage|@react-native-async-storage/.test(content);
|
|
218
|
+
const hasSecureStorage = /SecureStore|Keychain|EncryptedSharedPreferences/.test(content);
|
|
219
|
+
const hasTokenStorage = /token|jwt|auth.*storage/i.test(content);
|
|
220
|
+
if (hasTokenStorage && hasAsyncStorage && !hasSecureStorage) {
|
|
221
|
+
this.issues.push(`[Security] ${filename}: Auth tokens in AsyncStorage - use SecureStore!`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 8.2 Offline Handling
|
|
225
|
+
const hasNetwork = /fetch|axios|netinfo/.test(content);
|
|
226
|
+
const hasOffline = /offline|isConnected|netInfo/.test(content);
|
|
227
|
+
if (hasNetwork && !hasOffline) {
|
|
228
|
+
this.warnings.push(`[Offline] ${filename}: Network requests without offline handling`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// --- 9. ACCESSIBILITY ---
|
|
232
|
+
|
|
233
|
+
if (isReactNative) {
|
|
234
|
+
const hasPressable = /Pressable|TouchableOpacity/.test(content);
|
|
235
|
+
const hasA11yLabel = /accessibilityLabel|aria-label|testID/.test(content);
|
|
236
|
+
if (hasPressable && !hasA11yLabel) {
|
|
237
|
+
this.warnings.push(`[A11y] ${filename}: Touchable without accessibilityLabel`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// --- 10. ERROR HANDLING ---
|
|
242
|
+
|
|
243
|
+
if (isReactNative) {
|
|
244
|
+
const hasErrorBoundary = /ErrorBoundary|componentDidCatch/.test(content);
|
|
245
|
+
if (!hasErrorBoundary) {
|
|
246
|
+
this.warnings.push(`[Error] ${filename}: No ErrorBoundary detected`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
auditDirectory(directory) {
|
|
252
|
+
const extensions = new Set(['.tsx', '.ts', '.jsx', '.js', '.dart']);
|
|
253
|
+
const excludeDirs = new Set(['node_modules', '.git', 'dist', 'build', '.next', 'ios', 'android']);
|
|
254
|
+
|
|
255
|
+
const walk = (dir) => {
|
|
256
|
+
try {
|
|
257
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
258
|
+
for (const entry of entries) {
|
|
259
|
+
const fullPath = path.join(dir, entry.name);
|
|
260
|
+
if (entry.isDirectory()) {
|
|
261
|
+
if (!excludeDirs.has(entry.name)) {
|
|
262
|
+
walk(fullPath);
|
|
263
|
+
}
|
|
264
|
+
} else if (entry.isFile()) {
|
|
265
|
+
if (extensions.has(path.extname(entry.name))) {
|
|
266
|
+
this.auditFile(fullPath);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
} catch {
|
|
271
|
+
// Ignore permission errors
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
walk(directory);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
getReport() {
|
|
279
|
+
return {
|
|
280
|
+
files_checked: this.filesChecked,
|
|
281
|
+
issues: this.issues,
|
|
282
|
+
warnings: this.warnings,
|
|
283
|
+
passed_checks: this.passedCount,
|
|
284
|
+
compliant: this.issues.length === 0
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function main() {
|
|
290
|
+
const args = process.argv.slice(2);
|
|
291
|
+
if (args.length < 1) {
|
|
292
|
+
console.log('Usage: node mobile_audit.js <directory>');
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const targetPath = args[0];
|
|
297
|
+
const isJson = args.includes('--json');
|
|
298
|
+
|
|
299
|
+
const auditor = new MobileAuditor();
|
|
300
|
+
|
|
301
|
+
if (fs.statSync(targetPath).isFile()) {
|
|
302
|
+
auditor.auditFile(targetPath);
|
|
303
|
+
} else {
|
|
304
|
+
auditor.auditDirectory(targetPath);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const report = auditor.getReport();
|
|
308
|
+
|
|
309
|
+
if (isJson) {
|
|
310
|
+
console.log(JSON.stringify(report, null, 2));
|
|
311
|
+
} else {
|
|
312
|
+
console.log(`\n[MOBILE AUDIT] ${report.files_checked} mobile files checked`);
|
|
313
|
+
console.log('-'.repeat(50));
|
|
314
|
+
|
|
315
|
+
if (report.issues.length > 0) {
|
|
316
|
+
console.log(`[!] ISSUES (${report.issues.length}):`);
|
|
317
|
+
report.issues.slice(0, 10).forEach(i => console.log(` - ${i}`));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (report.warnings.length > 0) {
|
|
321
|
+
console.log(`[*] WARNINGS (${report.warnings.length}):`);
|
|
322
|
+
report.warnings.slice(0, 15).forEach(w => console.log(` - ${w}`));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
console.log(`[+] PASSED CHECKS: ${report.passed_checks}`);
|
|
326
|
+
const status = report.compliant ? 'PASS' : 'FAIL';
|
|
327
|
+
console.log(`STATUS: ${status}`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
process.exit(report.compliant ? 0 : 1);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
main();
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* TypeScript Project Diagnostic Script
|
|
4
|
+
* Analyzes TypeScript projects for configuration, performance, and common issues.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Run shell command and return output.
|
|
13
|
+
* @param {string} cmd
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
function runCmd(cmd) {
|
|
17
|
+
try {
|
|
18
|
+
return execSync(cmd, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
19
|
+
} catch (e) {
|
|
20
|
+
return e.stdout || e.stderr || e.message;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function checkVersions() {
|
|
25
|
+
console.log('\nđŚ Versions:');
|
|
26
|
+
console.log('-'.repeat(40));
|
|
27
|
+
|
|
28
|
+
const tsVersion = runCmd('npx tsc --version 2>/dev/null').trim();
|
|
29
|
+
const nodeVersion = runCmd('node -v 2>/dev/null').trim();
|
|
30
|
+
|
|
31
|
+
console.log(` TypeScript: ${tsVersion || 'Not found'}`);
|
|
32
|
+
console.log(` Node.js: ${nodeVersion || 'Not found'}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function checkTsconfig() {
|
|
36
|
+
console.log('\nâď¸ TSConfig Analysis:');
|
|
37
|
+
console.log('-'.repeat(40));
|
|
38
|
+
|
|
39
|
+
const tsconfigPath = 'tsconfig.json';
|
|
40
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
41
|
+
console.log('â ď¸ tsconfig.json not found');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const config = JSON.parse(fs.readFileSync(tsconfigPath, 'utf-8'));
|
|
47
|
+
const compilerOpts = config.compilerOptions || {};
|
|
48
|
+
|
|
49
|
+
// Check strict mode
|
|
50
|
+
if (compilerOpts.strict) {
|
|
51
|
+
console.log('â
Strict mode enabled');
|
|
52
|
+
} else {
|
|
53
|
+
console.log('â ď¸ Strict mode NOT enabled');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check important flags
|
|
57
|
+
const flags = {
|
|
58
|
+
noUncheckedIndexedAccess: 'Unchecked index access protection',
|
|
59
|
+
noImplicitOverride: 'Implicit override protection',
|
|
60
|
+
skipLibCheck: 'Skip lib check (performance)',
|
|
61
|
+
incremental: 'Incremental compilation'
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
for (const [flag, desc] of Object.entries(flags)) {
|
|
65
|
+
const status = compilerOpts[flag] ? 'â
' : 'âŞ';
|
|
66
|
+
console.log(` ${status} ${desc}: ${compilerOpts[flag] ?? 'not set'}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Check module settings
|
|
70
|
+
console.log(`\n Module: ${compilerOpts.module || 'not set'}`);
|
|
71
|
+
console.log(` Module Resolution: ${compilerOpts.moduleResolution || 'not set'}`);
|
|
72
|
+
console.log(` Target: ${compilerOpts.target || 'not set'}`);
|
|
73
|
+
|
|
74
|
+
} catch (e) {
|
|
75
|
+
console.log('â Invalid JSON in tsconfig.json');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function checkTooling() {
|
|
80
|
+
console.log('\nđ ď¸ Tooling Detection:');
|
|
81
|
+
console.log('-'.repeat(40));
|
|
82
|
+
|
|
83
|
+
const pkgPath = 'package.json';
|
|
84
|
+
if (!fs.existsSync(pkgPath)) {
|
|
85
|
+
console.log('â ď¸ package.json not found');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
91
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
92
|
+
|
|
93
|
+
const tools = {
|
|
94
|
+
biome: 'Biome (linter/formatter)',
|
|
95
|
+
eslint: 'ESLint',
|
|
96
|
+
prettier: 'Prettier',
|
|
97
|
+
vitest: 'Vitest (testing)',
|
|
98
|
+
jest: 'Jest (testing)',
|
|
99
|
+
turborepo: 'Turborepo (monorepo)',
|
|
100
|
+
turbo: 'Turbo (monorepo)',
|
|
101
|
+
nx: 'Nx (monorepo)',
|
|
102
|
+
lerna: 'Lerna (monorepo)'
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
for (const [tool, desc] of Object.entries(tools)) {
|
|
106
|
+
for (const dep of Object.keys(allDeps || {})) {
|
|
107
|
+
if (dep.toLowerCase().includes(tool)) {
|
|
108
|
+
console.log(` â
${desc}`);
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.log('â Invalid JSON in package.json');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function checkMonorepo() {
|
|
119
|
+
console.log('\nđŚ Monorepo Check:');
|
|
120
|
+
console.log('-'.repeat(40));
|
|
121
|
+
|
|
122
|
+
const indicators = [
|
|
123
|
+
['pnpm-workspace.yaml', 'PNPM Workspace'],
|
|
124
|
+
['lerna.json', 'Lerna'],
|
|
125
|
+
['nx.json', 'Nx'],
|
|
126
|
+
['turbo.json', 'Turborepo']
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
let found = false;
|
|
130
|
+
for (const [file, name] of indicators) {
|
|
131
|
+
if (fs.existsSync(file)) {
|
|
132
|
+
console.log(` â
${name} detected`);
|
|
133
|
+
found = true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!found) {
|
|
138
|
+
console.log(' ⪠No monorepo configuration detected');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function checkTypeErrors() {
|
|
143
|
+
console.log('\nđ Type Check:');
|
|
144
|
+
console.log('-'.repeat(40));
|
|
145
|
+
|
|
146
|
+
const result = runCmd('npx tsc --noEmit 2>&1');
|
|
147
|
+
if (result.includes('error TS')) {
|
|
148
|
+
const errors = (result.match(/error TS/g) || []).length;
|
|
149
|
+
console.log(` â ${errors}+ type errors found`);
|
|
150
|
+
console.log(result.slice(0, 500));
|
|
151
|
+
} else {
|
|
152
|
+
console.log(' â
No type errors');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function checkAnyUsage() {
|
|
157
|
+
console.log("\nâ ď¸ 'any' Type Usage:");
|
|
158
|
+
console.log('-'.repeat(40));
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const result = runCmd("grep -r ': any' --include='*.ts' --include='*.tsx' src/ 2>/dev/null | wc -l");
|
|
162
|
+
const count = result.trim();
|
|
163
|
+
if (count && count !== '0') {
|
|
164
|
+
console.log(` â ď¸ Found ${count} occurrences of ': any'`);
|
|
165
|
+
const sample = runCmd("grep -rn ': any' --include='*.ts' --include='*.tsx' src/ 2>/dev/null | head -5");
|
|
166
|
+
if (sample) console.log(sample);
|
|
167
|
+
} else {
|
|
168
|
+
console.log(" â
No explicit 'any' types found");
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
console.log(" ⪠Could not check (grep not available on Windows)");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function checkTypeAssertions() {
|
|
176
|
+
console.log('\nâ ď¸ Type Assertions (as):');
|
|
177
|
+
console.log('-'.repeat(40));
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const result = runCmd("grep -r ' as ' --include='*.ts' --include='*.tsx' src/ 2>/dev/null | grep -v 'import' | wc -l");
|
|
181
|
+
const count = result.trim();
|
|
182
|
+
if (count && count !== '0') {
|
|
183
|
+
console.log(` â ď¸ Found ${count} type assertions`);
|
|
184
|
+
} else {
|
|
185
|
+
console.log(' â
No type assertions found');
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
console.log(' ⪠Could not check (grep not available on Windows)');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function checkPerformance() {
|
|
193
|
+
console.log('\nâąď¸ Type Check Performance:');
|
|
194
|
+
console.log('-'.repeat(40));
|
|
195
|
+
|
|
196
|
+
const result = runCmd('npx tsc --extendedDiagnostics --noEmit 2>&1');
|
|
197
|
+
const lines = result.split('\n').filter(line =>
|
|
198
|
+
/Check time|Files:|Lines:|Nodes:/.test(line)
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (lines.length > 0) {
|
|
202
|
+
lines.forEach(line => console.log(` ${line}`));
|
|
203
|
+
} else {
|
|
204
|
+
console.log(' â ď¸ Could not measure performance');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function main() {
|
|
209
|
+
console.log('='.repeat(50));
|
|
210
|
+
console.log('đ TypeScript Project Diagnostic Report');
|
|
211
|
+
console.log('='.repeat(50));
|
|
212
|
+
|
|
213
|
+
checkVersions();
|
|
214
|
+
checkTsconfig();
|
|
215
|
+
checkTooling();
|
|
216
|
+
checkMonorepo();
|
|
217
|
+
checkAnyUsage();
|
|
218
|
+
checkTypeAssertions();
|
|
219
|
+
checkTypeErrors();
|
|
220
|
+
checkPerformance();
|
|
221
|
+
|
|
222
|
+
console.log('\n' + '='.repeat(50));
|
|
223
|
+
console.log('â
Diagnostic Complete');
|
|
224
|
+
console.log('='.repeat(50));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
main();
|