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.
Files changed (92) hide show
  1. package/README.md +77 -0
  2. package/agents/developer.md +56 -0
  3. package/agents/frontend-design.md +69 -0
  4. package/agents/mobile-reviewer.md +36 -0
  5. package/agents/review-code.md +49 -0
  6. package/agents/security-scanner.md +50 -0
  7. package/agents/tester.md +72 -0
  8. package/bin/cli.js +226 -0
  9. package/package.json +20 -0
  10. package/skills/ai-asset-generator/SKILL.md +255 -0
  11. package/skills/ai-asset-generator/docs/gen-image.md +274 -0
  12. package/skills/ai-asset-generator/docs/genvideo.md +341 -0
  13. package/skills/ai-asset-generator/docs/remove-background.md +19 -0
  14. package/skills/ai-asset-generator/generate-credit-assets.mjs +180 -0
  15. package/skills/ai-asset-generator/generate-ginbrowser-assets.mjs +242 -0
  16. package/skills/ai-asset-generator/generate-sty-icon.mjs +149 -0
  17. package/skills/ai-asset-generator/lib/bg-remove.mjs +34 -0
  18. package/skills/ai-asset-generator/lib/env.mjs +38 -0
  19. package/skills/ai-asset-generator/lib/kie-client.mjs +88 -0
  20. package/skills/ai-asset-generator/scripts/scaffold-generator.mjs +203 -0
  21. package/skills/ai-build-ai/SKILL.md +124 -0
  22. package/skills/ai-build-ai/docs/agent-teams.md +293 -0
  23. package/skills/ai-build-ai/docs/checkpointing.md +161 -0
  24. package/skills/ai-build-ai/docs/create-agent.md +399 -0
  25. package/skills/ai-build-ai/docs/create-mcp.md +395 -0
  26. package/skills/ai-build-ai/docs/create-skill.md +299 -0
  27. package/skills/ai-build-ai/docs/headless-mode.md +614 -0
  28. package/skills/ai-build-ai/docs/hooks.md +578 -0
  29. package/skills/ai-build-ai/docs/memory-claude-md.md +375 -0
  30. package/skills/ai-build-ai/docs/output-styles.md +208 -0
  31. package/skills/ai-build-ai/docs/overview.md +162 -0
  32. package/skills/ai-build-ai/docs/permissions.md +391 -0
  33. package/skills/ai-build-ai/docs/plugins.md +396 -0
  34. package/skills/ai-build-ai/docs/sandbox.md +262 -0
  35. package/skills/ai-build-ai/scripts/load-tutorial.sh +54 -0
  36. package/skills/icon-generator/SKILL.md +270 -0
  37. package/skills/mobile-app-review/SKILL.md +321 -0
  38. package/skills/mobile-app-review/references/apple-review.md +132 -0
  39. package/skills/mobile-app-review/references/google-play-review.md +203 -0
  40. package/skills/mongodb/SKILL.md +667 -0
  41. package/skills/mongodb/references/mongoose-patterns.md +368 -0
  42. package/skills/nestjs-architecture/SKILL.md +1086 -0
  43. package/skills/nestjs-architecture/references/advanced-patterns.md +590 -0
  44. package/skills/performance/SKILL.md +509 -0
  45. package/skills/react-fsd-architecture/SKILL.md +693 -0
  46. package/skills/react-fsd-architecture/references/fsd-patterns.md +747 -0
  47. package/skills/react-query/SKILL.md +685 -0
  48. package/skills/react-query/references/query-patterns.md +365 -0
  49. package/skills/review-code/SKILL.md +321 -0
  50. package/skills/review-code/references/clean-code-principles.md +395 -0
  51. package/skills/review-code/references/frontend-patterns.md +136 -0
  52. package/skills/review-code/references/nestjs-patterns.md +184 -0
  53. package/skills/review-code/scripts/check-module.sh +201 -0
  54. package/skills/review-code/scripts/deep-scan.sh +604 -0
  55. package/skills/review-code/scripts/dep-check.sh +522 -0
  56. package/skills/review-code/scripts/detect-duplicates.sh +466 -0
  57. package/skills/review-code/scripts/format-check.sh +577 -0
  58. package/skills/review-code/scripts/run-review.sh +167 -0
  59. package/skills/review-code/scripts/scan-codebase.sh +152 -0
  60. package/skills/security-scanner/SKILL.md +327 -0
  61. package/skills/security-scanner/references/nestjs-security.md +260 -0
  62. package/skills/security-scanner/references/nextjs-security.md +201 -0
  63. package/skills/security-scanner/references/react-native-security.md +199 -0
  64. package/skills/security-scanner/scripts/security-scan.sh +478 -0
  65. package/skills/ui-ux-pro-max/SKILL.md +377 -0
  66. package/skills/ui-ux-pro-max/data/charts.csv +26 -0
  67. package/skills/ui-ux-pro-max/data/colors.csv +97 -0
  68. package/skills/ui-ux-pro-max/data/icons.csv +101 -0
  69. package/skills/ui-ux-pro-max/data/landing.csv +31 -0
  70. package/skills/ui-ux-pro-max/data/products.csv +97 -0
  71. package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  72. package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  73. package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  74. package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  75. package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  76. package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  77. package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  78. package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  79. package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  80. package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  81. package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  82. package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  83. package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  84. package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  85. package/skills/ui-ux-pro-max/data/styles.csv +68 -0
  86. package/skills/ui-ux-pro-max/data/typography.csv +58 -0
  87. package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  88. package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  89. package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  90. package/skills/ui-ux-pro-max/scripts/core.py +253 -0
  91. package/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  92. 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