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,522 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ─────────────────────────────────────────────────────────────
|
|
3
|
+
# Dependency & Import Analyzer
|
|
4
|
+
#
|
|
5
|
+
# Detects circular dependencies, unused exports, orphan files,
|
|
6
|
+
# and import boundary violations. Pure bash — no npm deps.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# ./dep-check.sh # scan all projects
|
|
10
|
+
# ./dep-check.sh backend # scan backend only
|
|
11
|
+
# ./dep-check.sh frontend # scan frontend only
|
|
12
|
+
# ./dep-check.sh mobile # scan mobile only
|
|
13
|
+
# ./dep-check.sh --check circular # only circular deps
|
|
14
|
+
# ./dep-check.sh --check unused # only unused exports
|
|
15
|
+
# ./dep-check.sh --check orphans # only orphan files
|
|
16
|
+
# ./dep-check.sh --check boundaries # only import boundary violations
|
|
17
|
+
# ./dep-check.sh --summary # summary counts only
|
|
18
|
+
#
|
|
19
|
+
# Output: JSON to stdout
|
|
20
|
+
# Exit codes: 0 = clean, 1 = issues found, 2 = critical issues
|
|
21
|
+
# ─────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
set -uo pipefail
|
|
24
|
+
|
|
25
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
26
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../../../../.." && pwd)"
|
|
27
|
+
|
|
28
|
+
# ─── Defaults ────────────────────────────────────────────────
|
|
29
|
+
TARGET="all"
|
|
30
|
+
CHECK="all"
|
|
31
|
+
SUMMARY_ONLY=false
|
|
32
|
+
|
|
33
|
+
# ─── Auto-detect project directories ────────────────────────
|
|
34
|
+
detect_src() {
|
|
35
|
+
local kind="$1"; shift
|
|
36
|
+
for candidate in "$@"; do
|
|
37
|
+
for dir in $REPO_ROOT/$candidate; do
|
|
38
|
+
if [ -d "$dir/src" ]; then echo "$dir/src"; return; fi
|
|
39
|
+
if [ -d "$dir/app" ]; then echo "$dir/app"; return; fi
|
|
40
|
+
done
|
|
41
|
+
done
|
|
42
|
+
echo ""
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
detect_root() {
|
|
46
|
+
local kind="$1"; shift
|
|
47
|
+
for candidate in "$@"; do
|
|
48
|
+
for dir in $REPO_ROOT/$candidate; do
|
|
49
|
+
if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then echo "$dir"; return; fi
|
|
50
|
+
done
|
|
51
|
+
done
|
|
52
|
+
echo ""
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
BE_SRC="$(detect_src backend be-* backend server api)"
|
|
56
|
+
FE_SRC="$(detect_src frontend web-* frontend client)"
|
|
57
|
+
MB_SRC="$(detect_src mobile styai-mobile mobile)"
|
|
58
|
+
AUTH_SRC=""
|
|
59
|
+
[ -d "$REPO_ROOT/auth-package/src" ] && AUTH_SRC="$REPO_ROOT/auth-package/src"
|
|
60
|
+
|
|
61
|
+
BE_ROOT="$(detect_root backend be-* backend server api)"
|
|
62
|
+
FE_ROOT="$(detect_root frontend web-* frontend client)"
|
|
63
|
+
MB_ROOT="$(detect_root mobile styai-mobile mobile)"
|
|
64
|
+
|
|
65
|
+
# ─── Parse arguments ────────────────────────────────────────
|
|
66
|
+
while [[ $# -gt 0 ]]; do
|
|
67
|
+
case "$1" in
|
|
68
|
+
backend) TARGET="backend"; shift ;;
|
|
69
|
+
frontend) TARGET="frontend"; shift ;;
|
|
70
|
+
mobile) TARGET="mobile"; shift ;;
|
|
71
|
+
auth) TARGET="auth"; shift ;;
|
|
72
|
+
--check) CHECK="$2"; shift 2 ;;
|
|
73
|
+
--summary) SUMMARY_ONLY=true; shift ;;
|
|
74
|
+
*) shift ;;
|
|
75
|
+
esac
|
|
76
|
+
done
|
|
77
|
+
|
|
78
|
+
# ─── Build file list ────────────────────────────────────────
|
|
79
|
+
TMPDIR_WORK=$(mktemp -d)
|
|
80
|
+
trap 'rm -rf "$TMPDIR_WORK"' EXIT
|
|
81
|
+
|
|
82
|
+
FILES_LIST="$TMPDIR_WORK/files.txt"
|
|
83
|
+
FINDINGS_FILE="$TMPDIR_WORK/findings.jsonl"
|
|
84
|
+
touch "$FINDINGS_FILE"
|
|
85
|
+
|
|
86
|
+
build_file_list() {
|
|
87
|
+
local dirs=""
|
|
88
|
+
case "$TARGET" in
|
|
89
|
+
backend) [ -n "$BE_SRC" ] && dirs="$BE_SRC" ;;
|
|
90
|
+
frontend) [ -n "$FE_SRC" ] && dirs="$FE_SRC" ;;
|
|
91
|
+
mobile) [ -n "$MB_SRC" ] && dirs="$MB_SRC" ;;
|
|
92
|
+
auth) [ -n "$AUTH_SRC" ] && dirs="$AUTH_SRC" ;;
|
|
93
|
+
all)
|
|
94
|
+
[ -n "$BE_SRC" ] && dirs="$BE_SRC"
|
|
95
|
+
[ -n "$FE_SRC" ] && dirs="$dirs $FE_SRC"
|
|
96
|
+
[ -n "$MB_SRC" ] && dirs="$dirs $MB_SRC"
|
|
97
|
+
[ -n "$AUTH_SRC" ] && dirs="$dirs $AUTH_SRC"
|
|
98
|
+
;;
|
|
99
|
+
esac
|
|
100
|
+
dirs=$(echo "$dirs" | xargs)
|
|
101
|
+
[ -z "$dirs" ] && return
|
|
102
|
+
for dir in $dirs; do
|
|
103
|
+
find "$dir" \( -name "*.ts" -o -name "*.tsx" \) \
|
|
104
|
+
-not -path "*/node_modules/*" \
|
|
105
|
+
-not -path "*/.next/*" \
|
|
106
|
+
-not -path "*/dist/*" \
|
|
107
|
+
-not -path "*.d.ts" \
|
|
108
|
+
2>/dev/null
|
|
109
|
+
done
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
build_file_list | sort > "$FILES_LIST"
|
|
113
|
+
FILE_COUNT=$(wc -l < "$FILES_LIST" | tr -d ' ')
|
|
114
|
+
|
|
115
|
+
if [ "$FILE_COUNT" -eq 0 ]; then
|
|
116
|
+
echo '{"scan":"dep-check","findings":[],"summary":{"total":0}}'
|
|
117
|
+
exit 0
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# ─── Helpers ────────────────────────────────────────────────
|
|
121
|
+
relpath() { echo "${1#$REPO_ROOT/}"; }
|
|
122
|
+
|
|
123
|
+
json_escape() {
|
|
124
|
+
printf '%s' "$1" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()), end="")' 2>/dev/null || \
|
|
125
|
+
printf '"%s"' "$(printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g')"
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
count_matches() {
|
|
129
|
+
local result
|
|
130
|
+
result=$(grep -c "$1" "$2" 2>/dev/null) || true
|
|
131
|
+
echo "${result:-0}"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
add_finding() {
|
|
135
|
+
local check="$1" severity="$2" rule="$3" file="$4" line="$5" message="$6" suggestion="$7"
|
|
136
|
+
if [ "$CHECK" != "all" ] && [ "$CHECK" != "$check" ]; then return; fi
|
|
137
|
+
echo "{\"check\":\"$check\",\"severity\":\"$severity\",\"rule\":\"$rule\",\"file\":\"$(relpath "$file")\",\"line\":$line,\"message\":$(json_escape "$message"),\"suggestion\":$(json_escape "$suggestion")}" >> "$FINDINGS_FILE"
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# ═══════════════════════════════════════════════════════════════
|
|
141
|
+
# 1. CIRCULAR DEPENDENCY DETECTION
|
|
142
|
+
# ═══════════════════════════════════════════════════════════════
|
|
143
|
+
check_circular() {
|
|
144
|
+
local import_map="$TMPDIR_WORK/import_map.txt"
|
|
145
|
+
|
|
146
|
+
# Build import map: file -> imported_file
|
|
147
|
+
while IFS= read -r file; do
|
|
148
|
+
local dir
|
|
149
|
+
dir=$(dirname "$file")
|
|
150
|
+
# Extract import paths (relative imports only)
|
|
151
|
+
grep -n "^import.*from ['\"]\./" "$file" 2>/dev/null | while IFS=: read -r lineno content; do
|
|
152
|
+
# Extract the path
|
|
153
|
+
local import_path
|
|
154
|
+
import_path=$(echo "$content" | sed "s/.*from ['\"]//; s/['\"].*//" )
|
|
155
|
+
[ -z "$import_path" ] && continue
|
|
156
|
+
|
|
157
|
+
# Resolve to absolute file path
|
|
158
|
+
local resolved=""
|
|
159
|
+
for ext in "" ".ts" ".tsx" "/index.ts" "/index.tsx"; do
|
|
160
|
+
local candidate="$dir/$import_path$ext"
|
|
161
|
+
candidate=$(cd "$(dirname "$candidate")" 2>/dev/null && echo "$(pwd)/$(basename "$candidate")")
|
|
162
|
+
if [ -f "$candidate" ]; then
|
|
163
|
+
resolved="$candidate"
|
|
164
|
+
break
|
|
165
|
+
fi
|
|
166
|
+
done
|
|
167
|
+
|
|
168
|
+
[ -n "$resolved" ] && echo "$(relpath "$file") $(relpath "$resolved")"
|
|
169
|
+
done
|
|
170
|
+
done < "$FILES_LIST" > "$import_map"
|
|
171
|
+
|
|
172
|
+
# Detect cycles: A->B->A (direct circular)
|
|
173
|
+
sort "$import_map" | awk -F'\t' '
|
|
174
|
+
{
|
|
175
|
+
from = $1; to = $2
|
|
176
|
+
edges[from] = edges[from] " " to
|
|
177
|
+
# Check for direct cycle: A imports B AND B imports A
|
|
178
|
+
if (from == to) {
|
|
179
|
+
printf "self\t%s\t%s\n", from, from
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
END {
|
|
183
|
+
# Check bidirectional imports
|
|
184
|
+
for (a in edges) {
|
|
185
|
+
n = split(edges[a], targets, " ")
|
|
186
|
+
for (i = 1; i <= n; i++) {
|
|
187
|
+
b = targets[i]
|
|
188
|
+
if (b == "" || b == a) continue
|
|
189
|
+
# Check if b also imports a
|
|
190
|
+
m = split(edges[b], btargets, " ")
|
|
191
|
+
for (j = 1; j <= m; j++) {
|
|
192
|
+
if (btargets[j] == a) {
|
|
193
|
+
# Only print once (alphabetically first)
|
|
194
|
+
if (a < b) {
|
|
195
|
+
printf "mutual\t%s\t%s\n", a, b
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
' | sort -u | while IFS=$'\t' read -r kind file_a file_b; do
|
|
203
|
+
if [ "$kind" = "self" ]; then
|
|
204
|
+
add_finding "circular" "critical" "self_import" "$REPO_ROOT/$file_a" 1 \
|
|
205
|
+
"File imports itself" \
|
|
206
|
+
"Remove the self-referencing import"
|
|
207
|
+
else
|
|
208
|
+
add_finding "circular" "warning" "mutual_import" "$REPO_ROOT/$file_a" 1 \
|
|
209
|
+
"Circular dependency: $file_a <-> $file_b" \
|
|
210
|
+
"Break the cycle by extracting shared logic into a third module, or use dependency inversion"
|
|
211
|
+
fi
|
|
212
|
+
done
|
|
213
|
+
|
|
214
|
+
# Detect feature-to-feature cross-imports (NestJS modules importing each other)
|
|
215
|
+
if [ -n "$BE_SRC" ] && [ -d "$BE_SRC/features" ]; then
|
|
216
|
+
grep "features/" "$import_map" 2>/dev/null | awk -F'\t' '
|
|
217
|
+
{
|
|
218
|
+
from = $1; to = $2
|
|
219
|
+
# Extract feature names
|
|
220
|
+
split(from, from_parts, "/")
|
|
221
|
+
split(to, to_parts, "/")
|
|
222
|
+
from_feature = ""; to_feature = ""
|
|
223
|
+
for (i = 1; i <= length(from_parts); i++) {
|
|
224
|
+
if (from_parts[i] == "features" && i < length(from_parts)) { from_feature = from_parts[i+1]; break }
|
|
225
|
+
}
|
|
226
|
+
for (i = 1; i <= length(to_parts); i++) {
|
|
227
|
+
if (to_parts[i] == "features" && i < length(to_parts)) { to_feature = to_parts[i+1]; break }
|
|
228
|
+
}
|
|
229
|
+
if (from_feature != "" && to_feature != "" && from_feature != to_feature) {
|
|
230
|
+
key = from_feature "\t" to_feature
|
|
231
|
+
if (!(key in seen)) {
|
|
232
|
+
seen[key] = 1
|
|
233
|
+
printf "%s\t%s\t%s\n", from_feature, to_feature, from
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
' | while IFS=$'\t' read -r from_feat to_feat file; do
|
|
238
|
+
add_finding "circular" "info" "cross_feature_import" "$REPO_ROOT/$file" 1 \
|
|
239
|
+
"Feature '$from_feat' imports from feature '$to_feat'" \
|
|
240
|
+
"Feature modules should communicate through shared interfaces, not import directly from each other"
|
|
241
|
+
done
|
|
242
|
+
fi
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# ═══════════════════════════════════════════════════════════════
|
|
246
|
+
# 2. UNUSED EXPORTS DETECTION
|
|
247
|
+
# ═══════════════════════════════════════════════════════════════
|
|
248
|
+
check_unused() {
|
|
249
|
+
local exports_file="$TMPDIR_WORK/exports.txt"
|
|
250
|
+
local imports_file="$TMPDIR_WORK/all_imports.txt"
|
|
251
|
+
|
|
252
|
+
# Collect all exported symbols
|
|
253
|
+
while IFS= read -r file; do
|
|
254
|
+
# Named exports: export const/function/class/type/interface/enum NAME
|
|
255
|
+
grep -n "^export " "$file" 2>/dev/null | \
|
|
256
|
+
grep -v "^export default\|^export \*\|^export {" | \
|
|
257
|
+
sed 's/.*export \(const\|let\|var\|function\|class\|type\|interface\|enum\|async function\) //' | \
|
|
258
|
+
sed 's/[^a-zA-Z0-9_].*//' | while read -r name; do
|
|
259
|
+
[ -n "$name" ] && echo "$name $(relpath "$file")"
|
|
260
|
+
done
|
|
261
|
+
|
|
262
|
+
# Re-exports from index files are not counted as "unused"
|
|
263
|
+
done < "$FILES_LIST" > "$exports_file"
|
|
264
|
+
|
|
265
|
+
# Collect all import references across all files
|
|
266
|
+
while IFS= read -r file; do
|
|
267
|
+
grep "^import " "$file" 2>/dev/null | \
|
|
268
|
+
sed 's/.*{//; s/}.*//' | tr ',' '\n' | \
|
|
269
|
+
sed 's/[[:space:]]*as[[:space:]].*//; s/^[[:space:]]*//; s/[[:space:]]*$//' | \
|
|
270
|
+
grep -v '^$' | while read -r name; do
|
|
271
|
+
echo "$name"
|
|
272
|
+
done
|
|
273
|
+
done < "$FILES_LIST" | sort -u > "$imports_file"
|
|
274
|
+
|
|
275
|
+
# Find exports that are never imported anywhere
|
|
276
|
+
while IFS=$'\t' read -r name file; do
|
|
277
|
+
[ -z "$name" ] && continue
|
|
278
|
+
# Skip common framework exports (decorators, modules, etc.)
|
|
279
|
+
case "$name" in
|
|
280
|
+
Module|Controller|Service|Guard|Pipe|Filter|Interceptor|Gateway) continue ;;
|
|
281
|
+
App*|Main|bootstrap) continue ;;
|
|
282
|
+
esac
|
|
283
|
+
|
|
284
|
+
# Check if this export is referenced in any other file
|
|
285
|
+
if ! grep -q "^${name}$" "$imports_file" 2>/dev/null; then
|
|
286
|
+
# Double check: is it used in a dynamic import, decorator, or non-standard way?
|
|
287
|
+
local used
|
|
288
|
+
used=$(grep -rl "\b${name}\b" $(cat "$FILES_LIST") 2>/dev/null | grep -v "^${REPO_ROOT}/${file}$" | head -1)
|
|
289
|
+
if [ -z "$used" ]; then
|
|
290
|
+
local lineno
|
|
291
|
+
lineno=$(grep -n "export.*\b${name}\b" "$REPO_ROOT/$file" 2>/dev/null | head -1 | cut -d: -f1)
|
|
292
|
+
lineno=${lineno:-1}
|
|
293
|
+
add_finding "unused" "info" "unused_export" "$REPO_ROOT/$file" "$lineno" \
|
|
294
|
+
"Exported symbol '$name' is not imported by any other file" \
|
|
295
|
+
"Remove the export keyword if only used internally, or delete if truly unused"
|
|
296
|
+
fi
|
|
297
|
+
fi
|
|
298
|
+
done < "$exports_file"
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
# ═══════════════════════════════════════════════════════════════
|
|
302
|
+
# 3. ORPHAN FILE DETECTION
|
|
303
|
+
# ═══════════════════════════════════════════════════════════════
|
|
304
|
+
check_orphans() {
|
|
305
|
+
# Files that are never imported by any other file (potential dead code)
|
|
306
|
+
local all_imports="$TMPDIR_WORK/all_import_paths.txt"
|
|
307
|
+
|
|
308
|
+
# Collect all import targets
|
|
309
|
+
while IFS= read -r file; do
|
|
310
|
+
grep "^import.*from ['\"]" "$file" 2>/dev/null | \
|
|
311
|
+
sed "s/.*from ['\"]//; s/['\"].*//" | while read -r import_path; do
|
|
312
|
+
echo "$import_path"
|
|
313
|
+
done
|
|
314
|
+
done < "$FILES_LIST" | sort -u > "$all_imports"
|
|
315
|
+
|
|
316
|
+
# Check each file
|
|
317
|
+
while IFS= read -r file; do
|
|
318
|
+
local rel
|
|
319
|
+
rel=$(relpath "$file")
|
|
320
|
+
local basename_no_ext
|
|
321
|
+
basename_no_ext=$(basename "$file" | sed 's/\.[^.]*$//')
|
|
322
|
+
|
|
323
|
+
# Skip entry points and special files
|
|
324
|
+
case "$basename_no_ext" in
|
|
325
|
+
main|index|app|App|layout|page|loading|error|not-found) continue ;;
|
|
326
|
+
*.module|*.spec|*.test|*.config|*.d) continue ;;
|
|
327
|
+
_*) continue ;; # Convention: _ prefix = internal
|
|
328
|
+
esac
|
|
329
|
+
[[ "$file" == *"/index."* ]] && continue
|
|
330
|
+
[[ "$file" == *".module."* ]] && continue
|
|
331
|
+
[[ "$file" == *".spec."* || "$file" == *".test."* ]] && continue
|
|
332
|
+
[[ "$file" == *".config."* ]] && continue
|
|
333
|
+
|
|
334
|
+
# Check if this file is imported by anything
|
|
335
|
+
# Try various import path patterns
|
|
336
|
+
local found=false
|
|
337
|
+
local rel_no_ext
|
|
338
|
+
rel_no_ext=$(echo "$rel" | sed 's/\.[^.]*$//')
|
|
339
|
+
|
|
340
|
+
# Check if any import path matches this file
|
|
341
|
+
if grep -qF "$basename_no_ext" "$all_imports" 2>/dev/null; then
|
|
342
|
+
found=true
|
|
343
|
+
fi
|
|
344
|
+
|
|
345
|
+
# Also check for directory-relative imports that could point to this file
|
|
346
|
+
if [ "$found" = false ]; then
|
|
347
|
+
local dir_name
|
|
348
|
+
dir_name=$(basename "$(dirname "$file")")
|
|
349
|
+
if grep -qF "$dir_name/$basename_no_ext" "$all_imports" 2>/dev/null; then
|
|
350
|
+
found=true
|
|
351
|
+
fi
|
|
352
|
+
fi
|
|
353
|
+
|
|
354
|
+
if [ "$found" = false ]; then
|
|
355
|
+
add_finding "orphans" "info" "orphan_file" "$file" 1 \
|
|
356
|
+
"File '$(basename "$file")' is not imported by any other file" \
|
|
357
|
+
"Verify this file is used (may be dynamically loaded, or could be dead code to remove)"
|
|
358
|
+
fi
|
|
359
|
+
done < "$FILES_LIST"
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
# ═══════════════════════════════════════════════════════════════
|
|
363
|
+
# 4. IMPORT BOUNDARY VIOLATIONS
|
|
364
|
+
# ═══════════════════════════════════════════════════════════════
|
|
365
|
+
check_boundaries() {
|
|
366
|
+
while IFS= read -r file; do
|
|
367
|
+
local rel
|
|
368
|
+
rel=$(relpath "$file")
|
|
369
|
+
|
|
370
|
+
# Rule 1: Don't import from node_modules internals (deep imports)
|
|
371
|
+
grep -n "from '[^']*node_modules" "$file" 2>/dev/null | while IFS=: read -r lineno content; do
|
|
372
|
+
add_finding "boundaries" "warning" "node_modules_deep_import" "$file" "$lineno" \
|
|
373
|
+
"Direct import from node_modules path" \
|
|
374
|
+
"Import from the package name, not from node_modules internal path"
|
|
375
|
+
done
|
|
376
|
+
|
|
377
|
+
# Rule 2: Don't import from parent project (e.g., mobile importing from backend)
|
|
378
|
+
if [[ "$file" == *"/styai-mobile/"* ]]; then
|
|
379
|
+
grep -n "from '.*be-easycloset\|from '.*web-ui-easycloset" "$file" 2>/dev/null | while IFS=: read -r lineno content; do
|
|
380
|
+
add_finding "boundaries" "critical" "cross_project_import" "$file" "$lineno" \
|
|
381
|
+
"Mobile app imports directly from another project" \
|
|
382
|
+
"Use shared packages (auth-package) or API calls instead of direct imports"
|
|
383
|
+
done
|
|
384
|
+
fi
|
|
385
|
+
|
|
386
|
+
if [[ "$file" == *"/web-ui-easycloset/"* ]]; then
|
|
387
|
+
grep -n "from '.*be-easycloset\|from '.*styai-mobile" "$file" 2>/dev/null | while IFS=: read -r lineno content; do
|
|
388
|
+
add_finding "boundaries" "critical" "cross_project_import" "$file" "$lineno" \
|
|
389
|
+
"Frontend imports directly from another project" \
|
|
390
|
+
"Use shared packages or API calls instead of direct imports"
|
|
391
|
+
done
|
|
392
|
+
fi
|
|
393
|
+
|
|
394
|
+
# Rule 3: Don't import test utilities in production code
|
|
395
|
+
[[ "$file" == *".spec."* || "$file" == *".test."* ]] && continue
|
|
396
|
+
grep -n "from '.*__tests__\|from '.*\.spec\|from '.*\.test\|from '@testing-library\|from 'jest'" "$file" 2>/dev/null | while IFS=: read -r lineno content; do
|
|
397
|
+
add_finding "boundaries" "warning" "test_import_in_prod" "$file" "$lineno" \
|
|
398
|
+
"Test utility imported in production code" \
|
|
399
|
+
"Move test-related code to test files"
|
|
400
|
+
done
|
|
401
|
+
|
|
402
|
+
# Rule 4: Don't use relative imports that go too far up (../../../..)
|
|
403
|
+
grep -n "from '\.\./\.\./\.\./\.\." "$file" 2>/dev/null | while IFS=: read -r lineno content; do
|
|
404
|
+
add_finding "boundaries" "info" "deep_relative_import" "$file" "$lineno" \
|
|
405
|
+
"Deep relative import (4+ levels up)" \
|
|
406
|
+
"Use path aliases (@/, @features/, @shared/) instead of deep relative imports"
|
|
407
|
+
done
|
|
408
|
+
|
|
409
|
+
# Rule 5: Backend internal imports should use path aliases
|
|
410
|
+
if [[ "$file" == *"/be-easycloset/src/"* ]]; then
|
|
411
|
+
grep -n "from '\.\./\.\./\.\." "$file" 2>/dev/null | \
|
|
412
|
+
grep -v "\.spec\.\|\.test\." | while IFS=: read -r lineno content; do
|
|
413
|
+
add_finding "boundaries" "info" "missing_path_alias" "$file" "$lineno" \
|
|
414
|
+
"Deep relative import — consider using path alias" \
|
|
415
|
+
"Use @features/, @shared/, @core/ path aliases instead of ../../.."
|
|
416
|
+
done
|
|
417
|
+
fi
|
|
418
|
+
|
|
419
|
+
done < "$FILES_LIST"
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
# ═══════════════════════════════════════════════════════════════
|
|
423
|
+
# RUN CHECKS
|
|
424
|
+
# ═══════════════════════════════════════════════════════════════
|
|
425
|
+
|
|
426
|
+
if [ "$CHECK" = "all" ] || [ "$CHECK" = "circular" ]; then
|
|
427
|
+
check_circular
|
|
428
|
+
fi
|
|
429
|
+
|
|
430
|
+
if [ "$CHECK" = "all" ] || [ "$CHECK" = "unused" ]; then
|
|
431
|
+
check_unused
|
|
432
|
+
fi
|
|
433
|
+
|
|
434
|
+
if [ "$CHECK" = "all" ] || [ "$CHECK" = "orphans" ]; then
|
|
435
|
+
check_orphans
|
|
436
|
+
fi
|
|
437
|
+
|
|
438
|
+
if [ "$CHECK" = "all" ] || [ "$CHECK" = "boundaries" ]; then
|
|
439
|
+
check_boundaries
|
|
440
|
+
fi
|
|
441
|
+
|
|
442
|
+
# ═══════════════════════════════════════════════════════════════
|
|
443
|
+
# OUTPUT
|
|
444
|
+
# ═══════════════════════════════════════════════════════════════
|
|
445
|
+
|
|
446
|
+
TOTAL=$(wc -l < "$FINDINGS_FILE" | tr -d ' ')
|
|
447
|
+
CRITICAL=$(count_matches '"severity":"critical"' "$FINDINGS_FILE")
|
|
448
|
+
WARNINGS=$(count_matches '"severity":"warning"' "$FINDINGS_FILE")
|
|
449
|
+
INFO=$(count_matches '"severity":"info"' "$FINDINGS_FILE")
|
|
450
|
+
|
|
451
|
+
CIRCULAR_COUNT=$(count_matches '"check":"circular"' "$FINDINGS_FILE")
|
|
452
|
+
UNUSED_COUNT=$(count_matches '"check":"unused"' "$FINDINGS_FILE")
|
|
453
|
+
ORPHAN_COUNT=$(count_matches '"check":"orphans"' "$FINDINGS_FILE")
|
|
454
|
+
BOUNDARY_COUNT=$(count_matches '"check":"boundaries"' "$FINDINGS_FILE")
|
|
455
|
+
|
|
456
|
+
if [ "$SUMMARY_ONLY" = true ]; then
|
|
457
|
+
cat <<EOF
|
|
458
|
+
{
|
|
459
|
+
"scan": "dep-check",
|
|
460
|
+
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
461
|
+
"target": "$TARGET",
|
|
462
|
+
"files_scanned": $FILE_COUNT,
|
|
463
|
+
"summary": {
|
|
464
|
+
"total": $TOTAL,
|
|
465
|
+
"critical": $CRITICAL,
|
|
466
|
+
"warning": $WARNINGS,
|
|
467
|
+
"info": $INFO,
|
|
468
|
+
"by_check": {
|
|
469
|
+
"circular": $CIRCULAR_COUNT,
|
|
470
|
+
"unused": $UNUSED_COUNT,
|
|
471
|
+
"orphans": $ORPHAN_COUNT,
|
|
472
|
+
"boundaries": $BOUNDARY_COUNT
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
EOF
|
|
477
|
+
else
|
|
478
|
+
{
|
|
479
|
+
echo '{'
|
|
480
|
+
echo ' "scan": "dep-check",'
|
|
481
|
+
echo ' "timestamp": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",'
|
|
482
|
+
echo ' "target": "'"$TARGET"'",'
|
|
483
|
+
echo ' "files_scanned": '"$FILE_COUNT"','
|
|
484
|
+
echo ' "summary": {'
|
|
485
|
+
echo ' "total": '"$TOTAL"','
|
|
486
|
+
echo ' "critical": '"$CRITICAL"','
|
|
487
|
+
echo ' "warning": '"$WARNINGS"','
|
|
488
|
+
echo ' "info": '"$INFO"','
|
|
489
|
+
echo ' "by_check": {'
|
|
490
|
+
echo ' "circular": '"$CIRCULAR_COUNT"','
|
|
491
|
+
echo ' "unused": '"$UNUSED_COUNT"','
|
|
492
|
+
echo ' "orphans": '"$ORPHAN_COUNT"','
|
|
493
|
+
echo ' "boundaries": '"$BOUNDARY_COUNT"
|
|
494
|
+
echo ' }'
|
|
495
|
+
echo ' },'
|
|
496
|
+
echo ' "findings": ['
|
|
497
|
+
|
|
498
|
+
first=true
|
|
499
|
+
while IFS= read -r line; do
|
|
500
|
+
[ -z "$line" ] && continue
|
|
501
|
+
if [ "$first" = true ]; then
|
|
502
|
+
first=false
|
|
503
|
+
else
|
|
504
|
+
echo ','
|
|
505
|
+
fi
|
|
506
|
+
printf ' %s' "$line"
|
|
507
|
+
done < "$FINDINGS_FILE"
|
|
508
|
+
|
|
509
|
+
echo ''
|
|
510
|
+
echo ' ]'
|
|
511
|
+
echo '}'
|
|
512
|
+
}
|
|
513
|
+
fi
|
|
514
|
+
|
|
515
|
+
# ─── Exit code ──────────────────────────────────────────────
|
|
516
|
+
if [ "$CRITICAL" -gt 0 ]; then
|
|
517
|
+
exit 2
|
|
518
|
+
elif [ "$TOTAL" -gt 0 ]; then
|
|
519
|
+
exit 1
|
|
520
|
+
else
|
|
521
|
+
exit 0
|
|
522
|
+
fi
|