ginskill-init 1.0.0
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.md +77 -0
- package/agents/developer.md +56 -0
- package/agents/frontend-design.md +69 -0
- package/agents/mobile-reviewer.md +36 -0
- package/agents/review-code.md +49 -0
- package/agents/security-scanner.md +50 -0
- package/agents/tester.md +72 -0
- package/bin/cli.js +226 -0
- package/package.json +20 -0
- package/skills/ai-asset-generator/SKILL.md +255 -0
- package/skills/ai-asset-generator/docs/gen-image.md +274 -0
- package/skills/ai-asset-generator/docs/genvideo.md +341 -0
- package/skills/ai-asset-generator/docs/remove-background.md +19 -0
- package/skills/ai-asset-generator/generate-credit-assets.mjs +180 -0
- package/skills/ai-asset-generator/generate-ginbrowser-assets.mjs +242 -0
- package/skills/ai-asset-generator/generate-sty-icon.mjs +149 -0
- package/skills/ai-asset-generator/lib/bg-remove.mjs +34 -0
- package/skills/ai-asset-generator/lib/env.mjs +38 -0
- package/skills/ai-asset-generator/lib/kie-client.mjs +88 -0
- package/skills/ai-asset-generator/scripts/scaffold-generator.mjs +203 -0
- package/skills/ai-build-ai/SKILL.md +124 -0
- package/skills/ai-build-ai/docs/agent-teams.md +293 -0
- package/skills/ai-build-ai/docs/checkpointing.md +161 -0
- package/skills/ai-build-ai/docs/create-agent.md +399 -0
- package/skills/ai-build-ai/docs/create-mcp.md +395 -0
- package/skills/ai-build-ai/docs/create-skill.md +299 -0
- package/skills/ai-build-ai/docs/headless-mode.md +614 -0
- package/skills/ai-build-ai/docs/hooks.md +578 -0
- package/skills/ai-build-ai/docs/memory-claude-md.md +375 -0
- package/skills/ai-build-ai/docs/output-styles.md +208 -0
- package/skills/ai-build-ai/docs/overview.md +162 -0
- package/skills/ai-build-ai/docs/permissions.md +391 -0
- package/skills/ai-build-ai/docs/plugins.md +396 -0
- package/skills/ai-build-ai/docs/sandbox.md +262 -0
- package/skills/ai-build-ai/scripts/load-tutorial.sh +54 -0
- package/skills/icon-generator/SKILL.md +270 -0
- package/skills/mobile-app-review/SKILL.md +321 -0
- package/skills/mobile-app-review/references/apple-review.md +132 -0
- package/skills/mobile-app-review/references/google-play-review.md +203 -0
- package/skills/mongodb/SKILL.md +667 -0
- package/skills/mongodb/references/mongoose-patterns.md +368 -0
- package/skills/nestjs-architecture/SKILL.md +1086 -0
- package/skills/nestjs-architecture/references/advanced-patterns.md +590 -0
- package/skills/performance/SKILL.md +509 -0
- package/skills/react-fsd-architecture/SKILL.md +693 -0
- package/skills/react-fsd-architecture/references/fsd-patterns.md +747 -0
- package/skills/react-query/SKILL.md +685 -0
- package/skills/react-query/references/query-patterns.md +365 -0
- package/skills/review-code/SKILL.md +321 -0
- package/skills/review-code/references/clean-code-principles.md +395 -0
- package/skills/review-code/references/frontend-patterns.md +136 -0
- package/skills/review-code/references/nestjs-patterns.md +184 -0
- package/skills/review-code/scripts/check-module.sh +201 -0
- package/skills/review-code/scripts/deep-scan.sh +604 -0
- package/skills/review-code/scripts/dep-check.sh +522 -0
- package/skills/review-code/scripts/detect-duplicates.sh +466 -0
- package/skills/review-code/scripts/format-check.sh +577 -0
- package/skills/review-code/scripts/run-review.sh +167 -0
- package/skills/review-code/scripts/scan-codebase.sh +152 -0
- package/skills/security-scanner/SKILL.md +327 -0
- package/skills/security-scanner/references/nestjs-security.md +260 -0
- package/skills/security-scanner/references/nextjs-security.md +201 -0
- package/skills/security-scanner/references/react-native-security.md +199 -0
- package/skills/security-scanner/scripts/security-scan.sh +478 -0
- package/skills/ui-ux-pro-max/SKILL.md +377 -0
- package/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/skills/ui-ux-pro-max/scripts/search.py +114 -0
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ─────────────────────────────────────────────────────────────
|
|
3
|
+
# Format & Style Consistency Checker
|
|
4
|
+
#
|
|
5
|
+
# Detects format/style violations and configuration drift
|
|
6
|
+
# across the monorepo. Pure bash — no npm deps needed.
|
|
7
|
+
#
|
|
8
|
+
# What it checks:
|
|
9
|
+
# - Config drift (ESLint/Prettier/TSConfig inconsistencies)
|
|
10
|
+
# - Import ordering violations
|
|
11
|
+
# - File naming conventions
|
|
12
|
+
# - Consistent code patterns per project
|
|
13
|
+
# - Missing TypeScript strict flags
|
|
14
|
+
#
|
|
15
|
+
# Usage:
|
|
16
|
+
# ./format-check.sh # scan all projects
|
|
17
|
+
# ./format-check.sh backend # scan backend only
|
|
18
|
+
# ./format-check.sh frontend # scan frontend only
|
|
19
|
+
# ./format-check.sh mobile # scan mobile only
|
|
20
|
+
# ./format-check.sh --check configs # only config drift
|
|
21
|
+
# ./format-check.sh --check imports # only import ordering
|
|
22
|
+
# ./format-check.sh --check naming # only file naming
|
|
23
|
+
# ./format-check.sh --check patterns # only pattern consistency
|
|
24
|
+
# ./format-check.sh --summary # summary counts only
|
|
25
|
+
#
|
|
26
|
+
# Output: JSON to stdout
|
|
27
|
+
# Exit codes: 0 = clean, 1 = issues found
|
|
28
|
+
# ─────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
set -uo pipefail
|
|
31
|
+
|
|
32
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
33
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../../../../.." && pwd)"
|
|
34
|
+
|
|
35
|
+
# ─── Defaults ────────────────────────────────────────────────
|
|
36
|
+
TARGET="all"
|
|
37
|
+
CHECK="all"
|
|
38
|
+
SUMMARY_ONLY=false
|
|
39
|
+
|
|
40
|
+
# ─── Auto-detect project directories ────────────────────────
|
|
41
|
+
detect_root() {
|
|
42
|
+
local kind="$1"; shift
|
|
43
|
+
for candidate in "$@"; do
|
|
44
|
+
for dir in $REPO_ROOT/$candidate; do
|
|
45
|
+
if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then echo "$dir"; return; fi
|
|
46
|
+
done
|
|
47
|
+
done
|
|
48
|
+
echo ""
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
detect_src() {
|
|
52
|
+
local kind="$1"; shift
|
|
53
|
+
for candidate in "$@"; do
|
|
54
|
+
for dir in $REPO_ROOT/$candidate; do
|
|
55
|
+
if [ -d "$dir/src" ]; then echo "$dir/src"; return; fi
|
|
56
|
+
if [ -d "$dir/app" ]; then echo "$dir/app"; return; fi
|
|
57
|
+
done
|
|
58
|
+
done
|
|
59
|
+
echo ""
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
BE_ROOT="$(detect_root backend be-* backend server api)"
|
|
63
|
+
FE_ROOT="$(detect_root frontend web-* frontend client)"
|
|
64
|
+
MB_ROOT="$(detect_root mobile styai-mobile mobile)"
|
|
65
|
+
AUTH_ROOT=""
|
|
66
|
+
[ -d "$REPO_ROOT/auth-package" ] && AUTH_ROOT="$REPO_ROOT/auth-package"
|
|
67
|
+
|
|
68
|
+
BE_SRC="$(detect_src backend be-* backend server api)"
|
|
69
|
+
FE_SRC="$(detect_src frontend web-* frontend client)"
|
|
70
|
+
MB_SRC="$(detect_src mobile styai-mobile mobile)"
|
|
71
|
+
AUTH_SRC=""
|
|
72
|
+
[ -d "$REPO_ROOT/auth-package/src" ] && AUTH_SRC="$REPO_ROOT/auth-package/src"
|
|
73
|
+
|
|
74
|
+
# ─── Parse arguments ────────────────────────────────────────
|
|
75
|
+
while [[ $# -gt 0 ]]; do
|
|
76
|
+
case "$1" in
|
|
77
|
+
backend) TARGET="backend"; shift ;;
|
|
78
|
+
frontend) TARGET="frontend"; shift ;;
|
|
79
|
+
mobile) TARGET="mobile"; shift ;;
|
|
80
|
+
auth) TARGET="auth"; shift ;;
|
|
81
|
+
--check) CHECK="$2"; shift 2 ;;
|
|
82
|
+
--summary) SUMMARY_ONLY=true; shift ;;
|
|
83
|
+
*) shift ;;
|
|
84
|
+
esac
|
|
85
|
+
done
|
|
86
|
+
|
|
87
|
+
# ─── Build file list ────────────────────────────────────────
|
|
88
|
+
TMPDIR_WORK=$(mktemp -d)
|
|
89
|
+
trap 'rm -rf "$TMPDIR_WORK"' EXIT
|
|
90
|
+
|
|
91
|
+
FILES_LIST="$TMPDIR_WORK/files.txt"
|
|
92
|
+
FINDINGS_FILE="$TMPDIR_WORK/findings.jsonl"
|
|
93
|
+
touch "$FINDINGS_FILE"
|
|
94
|
+
|
|
95
|
+
build_file_list() {
|
|
96
|
+
local dirs=""
|
|
97
|
+
case "$TARGET" in
|
|
98
|
+
backend) [ -n "$BE_SRC" ] && dirs="$BE_SRC" ;;
|
|
99
|
+
frontend) [ -n "$FE_SRC" ] && dirs="$FE_SRC" ;;
|
|
100
|
+
mobile) [ -n "$MB_SRC" ] && dirs="$MB_SRC" ;;
|
|
101
|
+
auth) [ -n "$AUTH_SRC" ] && dirs="$AUTH_SRC" ;;
|
|
102
|
+
all)
|
|
103
|
+
[ -n "$BE_SRC" ] && dirs="$BE_SRC"
|
|
104
|
+
[ -n "$FE_SRC" ] && dirs="$dirs $FE_SRC"
|
|
105
|
+
[ -n "$MB_SRC" ] && dirs="$dirs $MB_SRC"
|
|
106
|
+
[ -n "$AUTH_SRC" ] && dirs="$dirs $AUTH_SRC"
|
|
107
|
+
;;
|
|
108
|
+
esac
|
|
109
|
+
dirs=$(echo "$dirs" | xargs)
|
|
110
|
+
[ -z "$dirs" ] && return
|
|
111
|
+
for dir in $dirs; do
|
|
112
|
+
find "$dir" \( -name "*.ts" -o -name "*.tsx" \) \
|
|
113
|
+
-not -path "*/node_modules/*" \
|
|
114
|
+
-not -path "*/.next/*" \
|
|
115
|
+
-not -path "*/dist/*" \
|
|
116
|
+
-not -path "*.d.ts" \
|
|
117
|
+
2>/dev/null
|
|
118
|
+
done
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
build_file_list | sort > "$FILES_LIST"
|
|
122
|
+
FILE_COUNT=$(wc -l < "$FILES_LIST" | tr -d ' ')
|
|
123
|
+
|
|
124
|
+
# ─── Helpers ────────────────────────────────────────────────
|
|
125
|
+
relpath() { echo "${1#$REPO_ROOT/}"; }
|
|
126
|
+
|
|
127
|
+
json_escape() {
|
|
128
|
+
printf '%s' "$1" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()), end="")' 2>/dev/null || \
|
|
129
|
+
printf '"%s"' "$(printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g')"
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
count_matches() {
|
|
133
|
+
local result
|
|
134
|
+
result=$(grep -c "$1" "$2" 2>/dev/null) || true
|
|
135
|
+
echo "${result:-0}"
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
add_finding() {
|
|
139
|
+
local check_type="$1" severity="$2" rule="$3" file="$4" line="$5" message="$6" suggestion="$7"
|
|
140
|
+
if [ "$CHECK" != "all" ] && [ "$CHECK" != "$check_type" ]; then return; fi
|
|
141
|
+
echo "{\"check\":\"$check_type\",\"severity\":\"$severity\",\"rule\":\"$rule\",\"file\":\"$(relpath "$file")\",\"line\":$line,\"message\":$(json_escape "$message"),\"suggestion\":$(json_escape "$suggestion")}" >> "$FINDINGS_FILE"
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# ═══════════════════════════════════════════════════════════════
|
|
145
|
+
# 1. CONFIG DRIFT DETECTION
|
|
146
|
+
# ═══════════════════════════════════════════════════════════════
|
|
147
|
+
check_configs() {
|
|
148
|
+
local projects=()
|
|
149
|
+
local project_roots=()
|
|
150
|
+
|
|
151
|
+
case "$TARGET" in
|
|
152
|
+
backend) [ -n "$BE_ROOT" ] && projects+=("backend") && project_roots+=("$BE_ROOT") ;;
|
|
153
|
+
frontend) [ -n "$FE_ROOT" ] && projects+=("frontend") && project_roots+=("$FE_ROOT") ;;
|
|
154
|
+
mobile) [ -n "$MB_ROOT" ] && projects+=("mobile") && project_roots+=("$MB_ROOT") ;;
|
|
155
|
+
auth) [ -n "$AUTH_ROOT" ] && projects+=("auth") && project_roots+=("$AUTH_ROOT") ;;
|
|
156
|
+
all)
|
|
157
|
+
[ -n "$BE_ROOT" ] && projects+=("backend") && project_roots+=("$BE_ROOT")
|
|
158
|
+
[ -n "$FE_ROOT" ] && projects+=("frontend") && project_roots+=("$FE_ROOT")
|
|
159
|
+
[ -n "$MB_ROOT" ] && projects+=("mobile") && project_roots+=("$MB_ROOT")
|
|
160
|
+
[ -n "$AUTH_ROOT" ] && projects+=("auth") && project_roots+=("$AUTH_ROOT")
|
|
161
|
+
;;
|
|
162
|
+
esac
|
|
163
|
+
|
|
164
|
+
[ ${#projects[@]} -eq 0 ] && return
|
|
165
|
+
|
|
166
|
+
# ─── Check ESLint config format consistency ──────────────
|
|
167
|
+
local eslint_formats=""
|
|
168
|
+
for i in "${!projects[@]}"; do
|
|
169
|
+
local root="${project_roots[$i]}"
|
|
170
|
+
local name="${projects[$i]}"
|
|
171
|
+
local format="none"
|
|
172
|
+
|
|
173
|
+
if [ -f "$root/eslint.config.mjs" ] || [ -f "$root/eslint.config.js" ]; then
|
|
174
|
+
format="flat"
|
|
175
|
+
elif [ -f "$root/.eslintrc.js" ] || [ -f "$root/.eslintrc.json" ] || [ -f "$root/.eslintrc.yml" ]; then
|
|
176
|
+
format="legacy"
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
eslint_formats="$eslint_formats $name:$format"
|
|
180
|
+
done
|
|
181
|
+
|
|
182
|
+
# Check if there's a mix
|
|
183
|
+
local has_flat=false has_legacy=false
|
|
184
|
+
for entry in $eslint_formats; do
|
|
185
|
+
case "$entry" in
|
|
186
|
+
*:flat) has_flat=true ;;
|
|
187
|
+
*:legacy) has_legacy=true ;;
|
|
188
|
+
esac
|
|
189
|
+
done
|
|
190
|
+
|
|
191
|
+
if [ "$has_flat" = true ] && [ "$has_legacy" = true ]; then
|
|
192
|
+
add_finding "configs" "warning" "eslint_format_drift" "$REPO_ROOT" 0 \
|
|
193
|
+
"Mixed ESLint config formats: $eslint_formats" \
|
|
194
|
+
"Standardize all projects to ESLint 9 flat config format (eslint.config.mjs)"
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
# ─── Check Prettier config consistency ───────────────────
|
|
198
|
+
local quote_styles=""
|
|
199
|
+
local semi_styles=""
|
|
200
|
+
local width_styles=""
|
|
201
|
+
|
|
202
|
+
for i in "${!projects[@]}"; do
|
|
203
|
+
local root="${project_roots[$i]}"
|
|
204
|
+
local name="${projects[$i]}"
|
|
205
|
+
|
|
206
|
+
for prettier_file in "$root/.prettierrc" "$root/.prettierrc.json" "$root/.prettierrc.js"; do
|
|
207
|
+
if [ -f "$prettier_file" ]; then
|
|
208
|
+
# Extract singleQuote
|
|
209
|
+
local sq
|
|
210
|
+
sq=$(grep -o '"singleQuote"[[:space:]]*:[[:space:]]*\(true\|false\)' "$prettier_file" 2>/dev/null | \
|
|
211
|
+
grep -o 'true\|false' | head -1)
|
|
212
|
+
sq=${sq:-"default"}
|
|
213
|
+
# Also check JS format
|
|
214
|
+
[ "$sq" = "default" ] && sq=$(grep -o "singleQuote[[:space:]]*:[[:space:]]*\(true\|false\)" "$prettier_file" 2>/dev/null | \
|
|
215
|
+
grep -o 'true\|false' | head -1)
|
|
216
|
+
sq=${sq:-"default"}
|
|
217
|
+
quote_styles="$quote_styles $name:$sq"
|
|
218
|
+
|
|
219
|
+
# Extract semi
|
|
220
|
+
local semi
|
|
221
|
+
semi=$(grep -o '"semi"[[:space:]]*:[[:space:]]*\(true\|false\)' "$prettier_file" 2>/dev/null | \
|
|
222
|
+
grep -o 'true\|false' | head -1)
|
|
223
|
+
[ -z "$semi" ] && semi=$(grep -o "semi[[:space:]]*:[[:space:]]*\(true\|false\)" "$prettier_file" 2>/dev/null | \
|
|
224
|
+
grep -o 'true\|false' | head -1)
|
|
225
|
+
semi=${semi:-"default"}
|
|
226
|
+
semi_styles="$semi_styles $name:$semi"
|
|
227
|
+
|
|
228
|
+
# Extract printWidth
|
|
229
|
+
local pw
|
|
230
|
+
pw=$(grep -oE '"printWidth"[[:space:]]*:[[:space:]]*[0-9]+' "$prettier_file" 2>/dev/null | \
|
|
231
|
+
grep -oE '[0-9]+' | head -1)
|
|
232
|
+
[ -z "$pw" ] && pw=$(grep -oE 'printWidth[[:space:]]*:[[:space:]]*[0-9]+' "$prettier_file" 2>/dev/null | \
|
|
233
|
+
grep -oE '[0-9]+' | head -1)
|
|
234
|
+
pw=${pw:-"default"}
|
|
235
|
+
width_styles="$width_styles $name:$pw"
|
|
236
|
+
break
|
|
237
|
+
fi
|
|
238
|
+
done
|
|
239
|
+
done
|
|
240
|
+
|
|
241
|
+
# Check for inconsistencies
|
|
242
|
+
local unique_quotes
|
|
243
|
+
unique_quotes=$(echo "$quote_styles" | tr ' ' '\n' | grep -v '^$' | cut -d: -f2 | sort -u | wc -l | tr -d ' ')
|
|
244
|
+
if [ "$unique_quotes" -gt 1 ]; then
|
|
245
|
+
add_finding "configs" "warning" "prettier_quote_drift" "$REPO_ROOT" 0 \
|
|
246
|
+
"Inconsistent Prettier quote style:$quote_styles" \
|
|
247
|
+
"Standardize singleQuote setting across all projects"
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
local unique_semi
|
|
251
|
+
unique_semi=$(echo "$semi_styles" | tr ' ' '\n' | grep -v '^$' | cut -d: -f2 | sort -u | wc -l | tr -d ' ')
|
|
252
|
+
if [ "$unique_semi" -gt 1 ]; then
|
|
253
|
+
add_finding "configs" "info" "prettier_semi_drift" "$REPO_ROOT" 0 \
|
|
254
|
+
"Inconsistent Prettier semicolon style:$semi_styles" \
|
|
255
|
+
"Consider standardizing semi setting across all projects"
|
|
256
|
+
fi
|
|
257
|
+
|
|
258
|
+
local unique_widths
|
|
259
|
+
unique_widths=$(echo "$width_styles" | tr ' ' '\n' | grep -v '^$' | cut -d: -f2 | sort -u | wc -l | tr -d ' ')
|
|
260
|
+
if [ "$unique_widths" -gt 1 ]; then
|
|
261
|
+
add_finding "configs" "info" "prettier_width_drift" "$REPO_ROOT" 0 \
|
|
262
|
+
"Inconsistent Prettier printWidth:$width_styles" \
|
|
263
|
+
"Consider standardizing printWidth across all projects"
|
|
264
|
+
fi
|
|
265
|
+
|
|
266
|
+
# ─── Check TypeScript strictness ─────────────────────────
|
|
267
|
+
for i in "${!projects[@]}"; do
|
|
268
|
+
local root="${project_roots[$i]}"
|
|
269
|
+
local name="${projects[$i]}"
|
|
270
|
+
local tsconfig="$root/tsconfig.json"
|
|
271
|
+
|
|
272
|
+
[ ! -f "$tsconfig" ] && continue
|
|
273
|
+
|
|
274
|
+
# Check for strict: false or missing strict
|
|
275
|
+
local has_strict
|
|
276
|
+
has_strict=$(grep '"strict"' "$tsconfig" 2>/dev/null | grep -o 'true\|false' | head -1)
|
|
277
|
+
has_strict=${has_strict:-"missing"}
|
|
278
|
+
|
|
279
|
+
if [ "$has_strict" = "false" ] || [ "$has_strict" = "missing" ]; then
|
|
280
|
+
add_finding "configs" "warning" "ts_not_strict" "$tsconfig" 1 \
|
|
281
|
+
"TypeScript strict mode is $has_strict in $name" \
|
|
282
|
+
"Enable \"strict\": true in tsconfig.json for better type safety"
|
|
283
|
+
fi
|
|
284
|
+
|
|
285
|
+
# Check for noImplicitAny: false (explicit unsafe opt-out)
|
|
286
|
+
if grep -q '"noImplicitAny"[[:space:]]*:[[:space:]]*false' "$tsconfig" 2>/dev/null; then
|
|
287
|
+
add_finding "configs" "warning" "ts_no_implicit_any_disabled" "$tsconfig" 1 \
|
|
288
|
+
"noImplicitAny explicitly disabled in $name" \
|
|
289
|
+
"Enable noImplicitAny to catch missing type annotations"
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
# Check strictNullChecks: false
|
|
293
|
+
if grep -q '"strictNullChecks"[[:space:]]*:[[:space:]]*false' "$tsconfig" 2>/dev/null; then
|
|
294
|
+
add_finding "configs" "warning" "ts_strict_null_disabled" "$tsconfig" 1 \
|
|
295
|
+
"strictNullChecks explicitly disabled in $name" \
|
|
296
|
+
"Enable strictNullChecks to prevent null/undefined errors at compile time"
|
|
297
|
+
fi
|
|
298
|
+
done
|
|
299
|
+
|
|
300
|
+
# ─── Check for missing configs ───────────────────────────
|
|
301
|
+
for i in "${!projects[@]}"; do
|
|
302
|
+
local root="${project_roots[$i]}"
|
|
303
|
+
local name="${projects[$i]}"
|
|
304
|
+
|
|
305
|
+
# Check for .prettierignore
|
|
306
|
+
if [ ! -f "$root/.prettierignore" ]; then
|
|
307
|
+
add_finding "configs" "info" "missing_prettierignore" "$root" 0 \
|
|
308
|
+
"No .prettierignore in $name" \
|
|
309
|
+
"Add .prettierignore to exclude dist/, node_modules/, coverage/ from formatting"
|
|
310
|
+
fi
|
|
311
|
+
|
|
312
|
+
# Check for .editorconfig
|
|
313
|
+
if [ ! -f "$root/.editorconfig" ] && [ ! -f "$REPO_ROOT/.editorconfig" ]; then
|
|
314
|
+
add_finding "configs" "info" "missing_editorconfig" "$root" 0 \
|
|
315
|
+
"No .editorconfig in $name or repo root" \
|
|
316
|
+
"Add .editorconfig to enforce consistent indentation across IDEs"
|
|
317
|
+
fi
|
|
318
|
+
done
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
# ═══════════════════════════════════════════════════════════════
|
|
322
|
+
# 2. IMPORT ORDERING VIOLATIONS
|
|
323
|
+
# ═══════════════════════════════════════════════════════════════
|
|
324
|
+
check_imports() {
|
|
325
|
+
while IFS= read -r file; do
|
|
326
|
+
[[ "$file" == *".spec."* || "$file" == *".test."* ]] && continue
|
|
327
|
+
|
|
328
|
+
# Check for unsorted imports: external imports should come before relative imports
|
|
329
|
+
awk -v fname="$file" '
|
|
330
|
+
/^import.*from/ {
|
|
331
|
+
if ($0 ~ /from ["\047]\./) {
|
|
332
|
+
# Relative import
|
|
333
|
+
if (had_relative == 0) first_relative = NR
|
|
334
|
+
had_relative = 1
|
|
335
|
+
} else {
|
|
336
|
+
# External import
|
|
337
|
+
if (had_relative) {
|
|
338
|
+
# External after relative = wrong order
|
|
339
|
+
printf "%s\t%d\n", fname, NR
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/^[^import]/ && !/^$/ && !/^\/\// { had_relative = 0 }
|
|
344
|
+
' "$file" 2>/dev/null
|
|
345
|
+
done < "$FILES_LIST" | sort -u -t$'\t' -k1,1 | head -30 | while IFS=$'\t' read -r file line; do
|
|
346
|
+
add_finding "imports" "info" "import_order" "$file" "$line" \
|
|
347
|
+
"External import after relative import — inconsistent ordering" \
|
|
348
|
+
"Group imports: 1) external packages, 2) path aliases (@/), 3) relative imports (./)"
|
|
349
|
+
done
|
|
350
|
+
|
|
351
|
+
# Check for wildcard imports (import * as) which hurt tree-shaking
|
|
352
|
+
while IFS= read -r file; do
|
|
353
|
+
[[ "$file" == *".spec."* || "$file" == *".test."* ]] && continue
|
|
354
|
+
grep -n "import \* as" "$file" 2>/dev/null | head -3 | while IFS=: read -r line content; do
|
|
355
|
+
add_finding "imports" "info" "wildcard_import" "$file" "$line" \
|
|
356
|
+
"Wildcard import (import * as) — prevents tree-shaking" \
|
|
357
|
+
"Use named imports instead: import { specific } from '...'"
|
|
358
|
+
done
|
|
359
|
+
done < "$FILES_LIST"
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
# ═══════════════════════════════════════════════════════════════
|
|
363
|
+
# 3. FILE NAMING CONVENTIONS
|
|
364
|
+
# ═══════════════════════════════════════════════════════════════
|
|
365
|
+
check_naming() {
|
|
366
|
+
while IFS= read -r file; do
|
|
367
|
+
local basename
|
|
368
|
+
basename=$(basename "$file")
|
|
369
|
+
|
|
370
|
+
# Check for camelCase files (should be kebab-case in most conventions)
|
|
371
|
+
if echo "$basename" | grep -qE '^[a-z]+[A-Z]' 2>/dev/null; then
|
|
372
|
+
# Skip common exceptions
|
|
373
|
+
case "$basename" in
|
|
374
|
+
*.module.ts|*.service.ts|*.controller.ts|*.dto.ts|*.entity.ts) ;; # NestJS convention
|
|
375
|
+
useQuery*|use[A-Z]*) ;; # React hooks
|
|
376
|
+
*)
|
|
377
|
+
add_finding "naming" "info" "camelcase_filename" "$file" 1 \
|
|
378
|
+
"File uses camelCase naming: $basename" \
|
|
379
|
+
"Use kebab-case for file names (e.g., my-component.ts instead of myComponent.ts)"
|
|
380
|
+
;;
|
|
381
|
+
esac
|
|
382
|
+
fi
|
|
383
|
+
|
|
384
|
+
# Check for PascalCase files that aren't React components
|
|
385
|
+
if echo "$basename" | grep -qE '^[A-Z][a-z]+[A-Z]' 2>/dev/null; then
|
|
386
|
+
if [[ "$basename" != *.tsx && "$basename" != *.jsx ]]; then
|
|
387
|
+
# PascalCase non-component files
|
|
388
|
+
case "$basename" in
|
|
389
|
+
*.module.ts|*.guard.ts|*.interceptor.ts|*.filter.ts|*.pipe.ts) ;; # NestJS convention
|
|
390
|
+
*.enum.ts|*.interface.ts|*.type.ts|*.constant.ts|*.config.ts) ;; # Type files
|
|
391
|
+
*)
|
|
392
|
+
add_finding "naming" "info" "pascalcase_non_component" "$file" 1 \
|
|
393
|
+
"Non-component file uses PascalCase: $basename" \
|
|
394
|
+
"Reserve PascalCase for React components (.tsx). Use kebab-case for utilities"
|
|
395
|
+
;;
|
|
396
|
+
esac
|
|
397
|
+
fi
|
|
398
|
+
fi
|
|
399
|
+
|
|
400
|
+
# Check for files with spaces or special characters
|
|
401
|
+
if echo "$basename" | grep -qE '[ ()\[\]!@#$%^&]' 2>/dev/null; then
|
|
402
|
+
add_finding "naming" "warning" "special_chars_filename" "$file" 1 \
|
|
403
|
+
"File name contains special characters: $basename" \
|
|
404
|
+
"Use only alphanumeric characters, hyphens, and dots in file names"
|
|
405
|
+
fi
|
|
406
|
+
|
|
407
|
+
# Check for inconsistent test file naming
|
|
408
|
+
if [[ "$basename" == *".test."* ]]; then
|
|
409
|
+
local src_file="${basename/.test/}"
|
|
410
|
+
# Check if there's a matching source file with .spec convention in same project
|
|
411
|
+
local dir
|
|
412
|
+
dir=$(dirname "$file")
|
|
413
|
+
if ls "$dir"/*.spec.* >/dev/null 2>&1; then
|
|
414
|
+
add_finding "naming" "info" "mixed_test_convention" "$file" 1 \
|
|
415
|
+
"Mixed test naming: '$basename' uses .test but sibling files use .spec" \
|
|
416
|
+
"Standardize on one convention (.spec or .test) per project"
|
|
417
|
+
fi
|
|
418
|
+
fi
|
|
419
|
+
done < "$FILES_LIST"
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
# ═══════════════════════════════════════════════════════════════
|
|
423
|
+
# 4. PATTERN CONSISTENCY
|
|
424
|
+
# ═══════════════════════════════════════════════════════════════
|
|
425
|
+
check_patterns() {
|
|
426
|
+
while IFS= read -r file; do
|
|
427
|
+
[[ "$file" == *".spec."* || "$file" == *".test."* ]] && continue
|
|
428
|
+
|
|
429
|
+
# Check for mixed export styles in same file
|
|
430
|
+
local has_default has_named
|
|
431
|
+
has_default=$(grep -c "^export default" "$file" 2>/dev/null) || true
|
|
432
|
+
has_named=$(grep -c "^export \(const\|function\|class\|type\|interface\|enum\)" "$file" 2>/dev/null) || true
|
|
433
|
+
has_default=${has_default:-0}
|
|
434
|
+
has_named=${has_named:-0}
|
|
435
|
+
|
|
436
|
+
if [ "$has_default" -gt 0 ] && [ "$has_named" -gt 2 ]; then
|
|
437
|
+
add_finding "patterns" "info" "mixed_exports" "$file" 1 \
|
|
438
|
+
"File has both default export and $has_named named exports" \
|
|
439
|
+
"Prefer consistent export style — either all named exports or a single default export"
|
|
440
|
+
fi
|
|
441
|
+
|
|
442
|
+
# Check for inconsistent async patterns (mixing .then() with async/await)
|
|
443
|
+
local has_then has_await
|
|
444
|
+
has_then=$(grep -c "\.then(" "$file" 2>/dev/null) || true
|
|
445
|
+
has_await=$(grep -c "await " "$file" 2>/dev/null) || true
|
|
446
|
+
has_then=${has_then:-0}
|
|
447
|
+
has_await=${has_await:-0}
|
|
448
|
+
|
|
449
|
+
if [ "$has_then" -gt 2 ] && [ "$has_await" -gt 2 ]; then
|
|
450
|
+
add_finding "patterns" "info" "mixed_async_patterns" "$file" 1 \
|
|
451
|
+
"File mixes .then() ($has_then) and async/await ($has_await) patterns" \
|
|
452
|
+
"Prefer async/await consistently — easier to read and debug"
|
|
453
|
+
fi
|
|
454
|
+
|
|
455
|
+
# Check for console.log that should be structured logging (backend only)
|
|
456
|
+
if [[ "$file" == *"/be-"* || "$file" == *"/backend/"* || "$file" == *"/server/"* ]]; then
|
|
457
|
+
local console_count
|
|
458
|
+
console_count=$(grep -c "console\.\(log\|warn\|error\|debug\)" "$file" 2>/dev/null) || true
|
|
459
|
+
console_count=${console_count:-0}
|
|
460
|
+
if [ "$console_count" -gt 3 ]; then
|
|
461
|
+
add_finding "patterns" "warning" "console_over_logger" "$file" 1 \
|
|
462
|
+
"File has $console_count console.* calls — should use structured logger" \
|
|
463
|
+
"Replace console.log with Logger from @nestjs/common or Pino for structured logging"
|
|
464
|
+
fi
|
|
465
|
+
fi
|
|
466
|
+
|
|
467
|
+
# Check for string concatenation in template-capable code
|
|
468
|
+
grep -n '+ *["\x27].*["\x27] *+\|["\x27].*["\x27] *+ *[a-zA-Z]' "$file" 2>/dev/null | \
|
|
469
|
+
grep -v "import\|require\|//\|console\.\|\.spec\.\|\.test\." | \
|
|
470
|
+
head -3 | while IFS=: read -r line content; do
|
|
471
|
+
add_finding "patterns" "info" "string_concat" "$file" "$line" \
|
|
472
|
+
"String concatenation — prefer template literals" \
|
|
473
|
+
"Use template literals (\`\${var}\`) instead of string concatenation for readability"
|
|
474
|
+
done
|
|
475
|
+
|
|
476
|
+
done < "$FILES_LIST"
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
# ═══════════════════════════════════════════════════════════════
|
|
480
|
+
# RUN CHECKS
|
|
481
|
+
# ═══════════════════════════════════════════════════════════════
|
|
482
|
+
|
|
483
|
+
if [ "$CHECK" = "all" ] || [ "$CHECK" = "configs" ]; then
|
|
484
|
+
check_configs
|
|
485
|
+
fi
|
|
486
|
+
|
|
487
|
+
if [ "$CHECK" = "all" ] || [ "$CHECK" = "imports" ]; then
|
|
488
|
+
check_imports
|
|
489
|
+
fi
|
|
490
|
+
|
|
491
|
+
if [ "$CHECK" = "all" ] || [ "$CHECK" = "naming" ]; then
|
|
492
|
+
check_naming
|
|
493
|
+
fi
|
|
494
|
+
|
|
495
|
+
if [ "$CHECK" = "all" ] || [ "$CHECK" = "patterns" ]; then
|
|
496
|
+
check_patterns
|
|
497
|
+
fi
|
|
498
|
+
|
|
499
|
+
# ═══════════════════════════════════════════════════════════════
|
|
500
|
+
# OUTPUT
|
|
501
|
+
# ═══════════════════════════════════════════════════════════════
|
|
502
|
+
|
|
503
|
+
TOTAL=$(wc -l < "$FINDINGS_FILE" | tr -d ' ')
|
|
504
|
+
CRITICAL=$(count_matches '"severity":"critical"' "$FINDINGS_FILE")
|
|
505
|
+
WARNINGS=$(count_matches '"severity":"warning"' "$FINDINGS_FILE")
|
|
506
|
+
INFO=$(count_matches '"severity":"info"' "$FINDINGS_FILE")
|
|
507
|
+
|
|
508
|
+
CONFIG_COUNT=$(count_matches '"check":"configs"' "$FINDINGS_FILE")
|
|
509
|
+
IMPORT_COUNT=$(count_matches '"check":"imports"' "$FINDINGS_FILE")
|
|
510
|
+
NAMING_COUNT=$(count_matches '"check":"naming"' "$FINDINGS_FILE")
|
|
511
|
+
PATTERN_COUNT=$(count_matches '"check":"patterns"' "$FINDINGS_FILE")
|
|
512
|
+
|
|
513
|
+
if [ "$SUMMARY_ONLY" = true ]; then
|
|
514
|
+
cat <<EOF
|
|
515
|
+
{
|
|
516
|
+
"scan": "format-check",
|
|
517
|
+
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
518
|
+
"target": "$TARGET",
|
|
519
|
+
"files_scanned": $FILE_COUNT,
|
|
520
|
+
"summary": {
|
|
521
|
+
"total": $TOTAL,
|
|
522
|
+
"critical": $CRITICAL,
|
|
523
|
+
"warning": $WARNINGS,
|
|
524
|
+
"info": $INFO,
|
|
525
|
+
"by_check": {
|
|
526
|
+
"configs": $CONFIG_COUNT,
|
|
527
|
+
"imports": $IMPORT_COUNT,
|
|
528
|
+
"naming": $NAMING_COUNT,
|
|
529
|
+
"patterns": $PATTERN_COUNT
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
EOF
|
|
534
|
+
else
|
|
535
|
+
{
|
|
536
|
+
echo '{'
|
|
537
|
+
echo ' "scan": "format-check",'
|
|
538
|
+
echo ' "timestamp": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",'
|
|
539
|
+
echo ' "target": "'"$TARGET"'",'
|
|
540
|
+
echo ' "files_scanned": '"$FILE_COUNT"','
|
|
541
|
+
echo ' "summary": {'
|
|
542
|
+
echo ' "total": '"$TOTAL"','
|
|
543
|
+
echo ' "critical": '"$CRITICAL"','
|
|
544
|
+
echo ' "warning": '"$WARNINGS"','
|
|
545
|
+
echo ' "info": '"$INFO"','
|
|
546
|
+
echo ' "by_check": {'
|
|
547
|
+
echo ' "configs": '"$CONFIG_COUNT"','
|
|
548
|
+
echo ' "imports": '"$IMPORT_COUNT"','
|
|
549
|
+
echo ' "naming": '"$NAMING_COUNT"','
|
|
550
|
+
echo ' "patterns": '"$PATTERN_COUNT"
|
|
551
|
+
echo ' }'
|
|
552
|
+
echo ' },'
|
|
553
|
+
echo ' "findings": ['
|
|
554
|
+
|
|
555
|
+
first=true
|
|
556
|
+
while IFS= read -r line; do
|
|
557
|
+
[ -z "$line" ] && continue
|
|
558
|
+
if [ "$first" = true ]; then
|
|
559
|
+
first=false
|
|
560
|
+
else
|
|
561
|
+
echo ','
|
|
562
|
+
fi
|
|
563
|
+
printf ' %s' "$line"
|
|
564
|
+
done < "$FINDINGS_FILE"
|
|
565
|
+
|
|
566
|
+
echo ''
|
|
567
|
+
echo ' ]'
|
|
568
|
+
echo '}'
|
|
569
|
+
}
|
|
570
|
+
fi
|
|
571
|
+
|
|
572
|
+
# ─── Exit code ──────────────────────────────────────────────
|
|
573
|
+
if [ "$TOTAL" -gt 0 ]; then
|
|
574
|
+
exit 1
|
|
575
|
+
else
|
|
576
|
+
exit 0
|
|
577
|
+
fi
|