gin-skills 1.0.3 → 1.0.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/landing/index.html +2 -2
- package/package.json +1 -1
- package/skills/react-native-performance/scanners/scan-animations.sh +11 -1
- package/skills/react-native-performance/scanners/scan-console-devtools.sh +51 -4
- package/skills/react-native-performance/scanners/scan-memory-leaks.sh +35 -0
- package/skills/react-native-performance/scanners/scan-rendering.sh +24 -1
- package/skills/react-native-performance/scanners/scan-state-network.sh +17 -1
package/landing/index.html
CHANGED
|
@@ -689,7 +689,7 @@
|
|
|
689
689
|
<div class="container">
|
|
690
690
|
<div class="hero-badge fade-up">
|
|
691
691
|
<span class="dot"></span>
|
|
692
|
-
<!-- AUTO:BADGE -->v1.0.
|
|
692
|
+
<!-- AUTO:BADGE -->v1.0.4 — 17 Skills · 6 Agents<!-- /AUTO:BADGE -->
|
|
693
693
|
</div>
|
|
694
694
|
<h1 class="fade-up"><span>Supercharge</span> Claude Code<br>with One Command</h1>
|
|
695
695
|
<p class="hero-sub fade-up">
|
|
@@ -1220,7 +1220,7 @@
|
|
|
1220
1220
|
<footer class="footer">
|
|
1221
1221
|
<div class="container">
|
|
1222
1222
|
<div class="footer-content">
|
|
1223
|
-
<div class="footer-brand">GinStudio Skills v1.0.
|
|
1223
|
+
<div class="footer-brand">GinStudio Skills v1.0.4</div>
|
|
1224
1224
|
<div class="footer-links">
|
|
1225
1225
|
<a href="https://www.npmjs.com/package/gin-skills" target="_blank" rel="noopener">npm</a>
|
|
1226
1226
|
<a href="https://github.com/vuduynhiennn/ginskills" target="_blank" rel="noopener">GitHub</a>
|
package/package.json
CHANGED
|
@@ -7,6 +7,8 @@ echo "Animation Performance Scanner"
|
|
|
7
7
|
echo "================================"
|
|
8
8
|
|
|
9
9
|
# Animated.timing/spring/decay without useNativeDriver: true
|
|
10
|
+
# Note: useNativeDriver only supports opacity and transform. Properties like backgroundColor,
|
|
11
|
+
# width, height, margin, padding, borderRadius REQUIRE useNativeDriver: false — not a bug.
|
|
10
12
|
echo ""
|
|
11
13
|
echo "--- Animated Without useNativeDriver ---"
|
|
12
14
|
grep -rn --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js" \
|
|
@@ -15,10 +17,18 @@ grep -rn --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js" \
|
|
|
15
17
|
| while IFS= read -r line; do
|
|
16
18
|
file=$(echo "$line" | cut -d: -f1)
|
|
17
19
|
lineno=$(echo "$line" | cut -d: -f2)
|
|
20
|
+
# Look at broader context (20 lines) to check what property the animated value drives
|
|
18
21
|
context=$(sed -n "${lineno},$((lineno + 8))p" "$file" 2>/dev/null)
|
|
22
|
+
broad_context=$(sed -n "1,$((lineno + 30))p" "$file" 2>/dev/null | tail -50)
|
|
19
23
|
if ! echo "$context" | grep -q 'useNativeDriver:\s*true'; then
|
|
20
24
|
if echo "$context" | grep -q 'useNativeDriver:\s*false'; then
|
|
21
|
-
|
|
25
|
+
# Check if the animated value is used for non-native-driver-compatible properties
|
|
26
|
+
# (backgroundColor, width, height, margin*, padding*, border*, left, top, right, bottom, flex)
|
|
27
|
+
if echo "$broad_context" | grep -qE 'backgroundColor|width:\s*anim|height:\s*anim|marginTop|marginBottom|marginLeft|marginRight|paddingTop|paddingBottom|paddingLeft|paddingRight|borderRadius|borderWidth|left:\s*anim|top:\s*anim|right:\s*anim|bottom:\s*anim|interpolate.*backgroundColor|interpolate.*color'; then
|
|
28
|
+
echo "$file:$lineno — [INFO] Animated with useNativeDriver: false (justified: animates layout/color property) → Consider react-native-reanimated for smoother layout animations on UI thread"
|
|
29
|
+
else
|
|
30
|
+
echo "$file:$lineno — [ERROR] Animated.timing/spring with useNativeDriver: false → Change to useNativeDriver: true; only opacity and transform are supported on native thread"
|
|
31
|
+
fi
|
|
22
32
|
else
|
|
23
33
|
echo "$file:$lineno — [ERROR] Animated.timing/spring missing useNativeDriver → Add useNativeDriver: true to run animation on UI thread and avoid JS thread jank"
|
|
24
34
|
fi
|
|
@@ -7,8 +7,19 @@ echo "Console & DevTools Scanner"
|
|
|
7
7
|
echo "================================"
|
|
8
8
|
|
|
9
9
|
# console.log without __DEV__ guard
|
|
10
|
+
# Escalates to ERROR when on gesture/animation hot paths (60fps concern)
|
|
11
|
+
# Projects using a custom logger (e.g. appLog) should prefer that over raw console.log
|
|
10
12
|
echo ""
|
|
11
13
|
echo "--- console.log Without __DEV__ Guard ---"
|
|
14
|
+
|
|
15
|
+
# Auto-detect custom logger: check if the project has an appLog/logger utility
|
|
16
|
+
CUSTOM_LOGGER=""
|
|
17
|
+
CUSTOM_LOGGER_IMPORT=""
|
|
18
|
+
if grep -rql --include="*.ts" --include="*.tsx" 'export const appLog' "$DIR" 2>/dev/null; then
|
|
19
|
+
CUSTOM_LOGGER="appLog"
|
|
20
|
+
CUSTOM_LOGGER_IMPORT='import { appLog } from "@/shared/utils/logger"'
|
|
21
|
+
fi
|
|
22
|
+
|
|
12
23
|
grep -rn --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js" \
|
|
13
24
|
'console\.log(' "$DIR" 2>/dev/null \
|
|
14
25
|
| grep -v node_modules | grep -v '__tests__' | grep -v '\.test\.' | grep -v '\.spec\.' \
|
|
@@ -25,7 +36,25 @@ grep -rn --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js" \
|
|
|
25
36
|
file_context=$(sed -n "1,$((lineno - 1))p" "$file" 2>/dev/null)
|
|
26
37
|
if ! echo "$context" | grep -q '__DEV__\|if.*DEV' && \
|
|
27
38
|
! echo "$file_context" | grep -q '= __DEV__'; then
|
|
28
|
-
|
|
39
|
+
# Check if this console.log is on a gesture/animation hot path (runs every frame)
|
|
40
|
+
hot_path_context=$(sed -n "$((lineno > 20 ? lineno - 20 : 1)),${lineno}p" "$file" 2>/dev/null)
|
|
41
|
+
is_worklet=false
|
|
42
|
+
if echo "$hot_path_context" | grep -q '"worklet"\|'\''worklet'\'''; then
|
|
43
|
+
is_worklet=true
|
|
44
|
+
fi
|
|
45
|
+
if echo "$hot_path_context" | grep -qE 'onUpdate|onActive|Pan\.|onMove|onScroll|onGestureEvent|Gesture\.|worklet|runOnJS|requestAnimationFrame|\.addListener'; then
|
|
46
|
+
if [ "$is_worklet" = true ]; then
|
|
47
|
+
echo "$file:$lineno — [ERROR] console.log in Reanimated worklet (runs on UI thread every frame) → Wrap with if (__DEV__) console.log(...); appLog cannot run in worklets"
|
|
48
|
+
else
|
|
49
|
+
echo "$file:$lineno — [ERROR] console.log on gesture/animation hot path (runs every frame at 60fps) → Remove or replace with ${CUSTOM_LOGGER:-__DEV__ guard}; serialization on hot paths causes jank"
|
|
50
|
+
fi
|
|
51
|
+
else
|
|
52
|
+
if [ -n "$CUSTOM_LOGGER" ]; then
|
|
53
|
+
echo "$file:$lineno — [WARN] raw console.log instead of project logger → Replace with $CUSTOM_LOGGER.debug(...) which is dev-only by default"
|
|
54
|
+
else
|
|
55
|
+
echo "$file:$lineno — [WARN] console.log without __DEV__ guard → Wrap with if (__DEV__) or use babel-plugin-transform-remove-console"
|
|
56
|
+
fi
|
|
57
|
+
fi
|
|
29
58
|
ISSUES=$((ISSUES + 1))
|
|
30
59
|
fi
|
|
31
60
|
done
|
|
@@ -47,7 +76,11 @@ grep -rn --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js" \
|
|
|
47
76
|
file_context=$(sed -n "1,$((lineno - 1))p" "$file" 2>/dev/null)
|
|
48
77
|
if ! echo "$context" | grep -q '__DEV__\|if.*DEV' && \
|
|
49
78
|
! echo "$file_context" | grep -q '= __DEV__'; then
|
|
50
|
-
|
|
79
|
+
if [ -n "$CUSTOM_LOGGER" ]; then
|
|
80
|
+
echo "$file:$lineno — [WARN] raw console.warn instead of project logger → Replace with $CUSTOM_LOGGER.warn(...) which is dev-only by default"
|
|
81
|
+
else
|
|
82
|
+
echo "$file:$lineno — [WARN] console.warn without __DEV__ guard → Wrap with if (__DEV__) or suppress in production logger"
|
|
83
|
+
fi
|
|
51
84
|
ISSUES=$((ISSUES + 1))
|
|
52
85
|
fi
|
|
53
86
|
done
|
|
@@ -61,7 +94,11 @@ grep -rn --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js" \
|
|
|
61
94
|
| grep -v '__DEV__\|Sentry\|crashlytics\|bugsnag' \
|
|
62
95
|
| while IFS= read -r line; do
|
|
63
96
|
file_loc=$(echo "$line" | cut -d: -f1,2)
|
|
64
|
-
|
|
97
|
+
if [ -n "$CUSTOM_LOGGER" ]; then
|
|
98
|
+
echo "$file_loc — [INFO] raw console.error → Replace with $CUSTOM_LOGGER.error(...) or use a crash reporting SDK (Sentry/Crashlytics)"
|
|
99
|
+
else
|
|
100
|
+
echo "$file_loc — [INFO] console.error in production code → Use a crash reporting SDK (Sentry/Crashlytics) instead of console.error"
|
|
101
|
+
fi
|
|
65
102
|
ISSUES=$((ISSUES + 1))
|
|
66
103
|
done
|
|
67
104
|
|
|
@@ -73,7 +110,11 @@ grep -rn --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js" \
|
|
|
73
110
|
| grep -v node_modules | grep -v '__tests__' | grep -v '\.test\.' | grep -v '\.spec\.' \
|
|
74
111
|
| while IFS= read -r line; do
|
|
75
112
|
file_loc=$(echo "$line" | cut -d: -f1,2)
|
|
76
|
-
|
|
113
|
+
if [ -n "$CUSTOM_LOGGER" ]; then
|
|
114
|
+
echo "$file_loc — [WARN] raw console.info/debug → Replace with $CUSTOM_LOGGER.debug(...) which is dev-only by default"
|
|
115
|
+
else
|
|
116
|
+
echo "$file_loc — [WARN] console.info/debug in production code → Remove or guard with if (__DEV__)"
|
|
117
|
+
fi
|
|
77
118
|
ISSUES=$((ISSUES + 1))
|
|
78
119
|
done
|
|
79
120
|
|
|
@@ -140,6 +181,7 @@ grep -rn --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js" \
|
|
|
140
181
|
done
|
|
141
182
|
|
|
142
183
|
# Production-critical logic inside __DEV__ blocks (anti-pattern)
|
|
184
|
+
# Skip __DEV__ blocks that only contain console.log (this is the correct pattern for worklet code)
|
|
143
185
|
echo ""
|
|
144
186
|
echo "--- Production Logic Inside __DEV__ Block ---"
|
|
145
187
|
grep -rn --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js" \
|
|
@@ -149,6 +191,11 @@ grep -rn --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js" \
|
|
|
149
191
|
file=$(echo "$line" | cut -d: -f1)
|
|
150
192
|
lineno=$(echo "$line" | cut -d: -f2)
|
|
151
193
|
context=$(sed -n "${lineno},$((lineno + 10))p" "$file" 2>/dev/null)
|
|
194
|
+
# Skip if the __DEV__ block only wraps console.log/warn/error (that's the correct pattern)
|
|
195
|
+
match_line=$(sed -n "${lineno}p" "$file" 2>/dev/null)
|
|
196
|
+
if echo "$match_line" | grep -q 'console\.\(log\|warn\|error\|debug\|info\)'; then
|
|
197
|
+
continue
|
|
198
|
+
fi
|
|
152
199
|
if echo "$context" | grep -q 'navigate\|dispatch\|setState\|fetch(\|api\.'; then
|
|
153
200
|
echo "$file:$lineno — [WARN] Production-critical logic inside __DEV__ block → State changes/navigation inside __DEV__ blocks will not run in production; this may be a bug"
|
|
154
201
|
ISSUES=$((ISSUES + 1))
|
|
@@ -151,6 +151,7 @@ grep -rln --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js"
|
|
|
151
151
|
# subscribe, addEventListener, setInterval, AppState) catch these patterns more reliably.
|
|
152
152
|
|
|
153
153
|
# AppState.addEventListener without remove
|
|
154
|
+
# Skip module-level singletons and class constructors — they intentionally persist for app lifetime
|
|
154
155
|
echo ""
|
|
155
156
|
echo "--- AppState.addEventListener Without Cleanup ---"
|
|
156
157
|
grep -rln --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js" 'AppState' "$DIR" 2>/dev/null \
|
|
@@ -158,8 +159,19 @@ grep -rln --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js"
|
|
|
158
159
|
| while IFS= read -r file; do
|
|
159
160
|
if grep -q 'AppState\.addEventListener\|AppState\.addListener' "$file" \
|
|
160
161
|
&& ! grep -q '\.remove()\|removeEventListener' "$file"; then
|
|
162
|
+
# Only flag files that use useEffect (React component/hook context)
|
|
163
|
+
# Module-level listeners and class constructor listeners are singletons — no cleanup needed
|
|
164
|
+
if ! grep -q 'useEffect' "$file"; then
|
|
165
|
+
continue
|
|
166
|
+
fi
|
|
161
167
|
grep -n 'AppState\.addEventListener\|AppState\.addListener' "$file" | while IFS= read -r match; do
|
|
162
168
|
lineno=$(echo "$match" | cut -d: -f1)
|
|
169
|
+
# Skip if the line is inside a class constructor or module-level scope
|
|
170
|
+
# Check if there's a class/constructor context above this line
|
|
171
|
+
preceding=$(sed -n "1,${lineno}p" "$file" 2>/dev/null)
|
|
172
|
+
if echo "$preceding" | grep -q 'class .*{' && echo "$preceding" | grep -q 'constructor('; then
|
|
173
|
+
continue
|
|
174
|
+
fi
|
|
163
175
|
echo "$file:$lineno — [ERROR] AppState.addEventListener without cleanup → Store subscription and call subscription.remove() in useEffect cleanup"
|
|
164
176
|
ISSUES=$((ISSUES + 1))
|
|
165
177
|
done
|
|
@@ -231,6 +243,29 @@ grep -rln --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js"
|
|
|
231
243
|
fi
|
|
232
244
|
done
|
|
233
245
|
|
|
246
|
+
# AbortSignal.addEventListener("abort", ...) without { once: true }
|
|
247
|
+
# When composing AbortControllers (e.g. timeout + user cancel), each listener on the
|
|
248
|
+
# upstream signal accumulates if not cleaned up. { once: true } auto-removes after firing.
|
|
249
|
+
echo ""
|
|
250
|
+
echo "--- AbortSignal addEventListener Without { once: true } ---"
|
|
251
|
+
grep -rn --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js" \
|
|
252
|
+
'\.addEventListener.*abort' "$DIR" 2>/dev/null \
|
|
253
|
+
| grep -v node_modules | grep -v '__tests__' | grep -v '\.test\.' | grep -v '\.spec\.' \
|
|
254
|
+
| while IFS= read -r line; do
|
|
255
|
+
file=$(echo "$line" | cut -d: -f1)
|
|
256
|
+
lineno=$(echo "$line" | cut -d: -f2)
|
|
257
|
+
match_text=$(echo "$line" | cut -d: -f3-)
|
|
258
|
+
# Check if { once: true } is present on the same line or within the next 2 lines
|
|
259
|
+
context=$(sed -n "${lineno},$((lineno + 2))p" "$file" 2>/dev/null)
|
|
260
|
+
if ! echo "$context" | grep -q 'once.*true\|{ once: true }'; then
|
|
261
|
+
# Check if there's a matching removeEventListener for cleanup
|
|
262
|
+
if ! grep -q 'removeEventListener.*abort' "$file"; then
|
|
263
|
+
echo "$file:$lineno — [WARN] addEventListener(\"abort\", fn) without { once: true } → Listeners accumulate on reused signals causing memory leak; add { once: true } as 3rd arg or call removeEventListener in cleanup"
|
|
264
|
+
ISSUES=$((ISSUES + 1))
|
|
265
|
+
fi
|
|
266
|
+
fi
|
|
267
|
+
done
|
|
268
|
+
|
|
234
269
|
# Reanimated useAnimatedReaction without cancelAnimation cleanup
|
|
235
270
|
echo ""
|
|
236
271
|
echo "--- useAnimatedReaction Without cancelAnimation Cleanup ---"
|
|
@@ -30,12 +30,35 @@ grep -rn --include="*.tsx" --include="*.jsx" --include="*.ts" --include="*.js" '
|
|
|
30
30
|
done
|
|
31
31
|
|
|
32
32
|
# key={index} or key={i} or key={idx}
|
|
33
|
+
# Skip static/skeleton lists where index keys are safe (never reordered, no unique IDs)
|
|
33
34
|
echo ""
|
|
34
35
|
echo "--- Index as List Key ---"
|
|
35
36
|
grep -rn --include="*.tsx" --include="*.jsx" 'key={index}\|key={i}\b\|key={idx}' "$DIR" 2>/dev/null \
|
|
36
37
|
| grep -v node_modules \
|
|
37
38
|
| while IFS= read -r line; do
|
|
38
|
-
|
|
39
|
+
file=$(echo "$line" | cut -d: -f1)
|
|
40
|
+
lineno=$(echo "$line" | cut -d: -f2)
|
|
41
|
+
file_loc="$file:$lineno"
|
|
42
|
+
content=$(echo "$line" | cut -d: -f3-)
|
|
43
|
+
|
|
44
|
+
# Check surrounding context (5 lines before, 2 after) for skeleton/static patterns
|
|
45
|
+
context=$(sed -n "$((lineno > 5 ? lineno - 5 : 1)),$((lineno + 2))p" "$file" 2>/dev/null)
|
|
46
|
+
|
|
47
|
+
# Skip 1: Static skeleton arrays (Array.from, Array(N), new Array)
|
|
48
|
+
if echo "$context" | grep -qE 'Array\.from|Array\([0-9]|new Array|\.fill\(|Skeleton|skeleton'; then
|
|
49
|
+
continue
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Skip 2: Files that are skeleton components (filename contains skeleton/loading)
|
|
53
|
+
if echo "$file" | grep -qiE 'skeleton|loading-skeleton|placeholder'; then
|
|
54
|
+
continue
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# Skip 3: Static enum/constant arrays that never reorder
|
|
58
|
+
if echo "$context" | grep -qE 'const\s+\[.*\]\s*=|\.entries\(\)|Object\.keys|Object\.values'; then
|
|
59
|
+
continue
|
|
60
|
+
fi
|
|
61
|
+
|
|
39
62
|
echo "$file_loc — [ERROR] Using index as key causes O(n) re-renders → Use stable unique ID (item.id)"
|
|
40
63
|
ISSUES=$((ISSUES + 1))
|
|
41
64
|
done
|
|
@@ -44,13 +44,29 @@ grep -rln --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js"
|
|
|
44
44
|
done
|
|
45
45
|
|
|
46
46
|
# Zustand store subscribed without selector
|
|
47
|
+
# Skip commented-out code and store definition files (where the hook is created, not called)
|
|
47
48
|
echo ""
|
|
48
49
|
echo "--- Zustand Store Without Selector ---"
|
|
49
50
|
grep -rn --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js" \
|
|
50
51
|
'useStore()\|use[A-Z][a-zA-Z]*Store()' "$DIR" 2>/dev/null \
|
|
51
52
|
| grep -v node_modules | grep -v '__tests__' | grep -v '\.test\.' | grep -v '\.spec\.' \
|
|
52
53
|
| while IFS= read -r line; do
|
|
53
|
-
|
|
54
|
+
file=$(echo "$line" | cut -d: -f1)
|
|
55
|
+
lineno=$(echo "$line" | cut -d: -f2)
|
|
56
|
+
content=$(echo "$line" | cut -d: -f3-)
|
|
57
|
+
file_loc="$file:$lineno"
|
|
58
|
+
|
|
59
|
+
# Skip commented-out lines (// or * at start, or inside block comments)
|
|
60
|
+
trimmed=$(echo "$content" | sed 's/^[[:space:]]*//')
|
|
61
|
+
if echo "$trimmed" | grep -qE '^\s*(//|/?\*|\*)'; then
|
|
62
|
+
continue
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Skip store definition files (where create() defines the hook)
|
|
66
|
+
if grep -q 'create(' "$file" && grep -q 'zustand\|from.*create' "$file"; then
|
|
67
|
+
continue
|
|
68
|
+
fi
|
|
69
|
+
|
|
54
70
|
echo "$file_loc — [ERROR] Zustand store subscribed without selector → Pass a selector: useStore(state => state.field) to avoid re-renders on unrelated state changes"
|
|
55
71
|
ISSUES=$((ISSUES + 1))
|
|
56
72
|
done
|