monol-plugin-scout 2.1.3 → 4.1.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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +244 -0
- package/monol-plugin-scout-pkg/CLAUDE.md +125 -8
- package/monol-plugin-scout-pkg/api/mock/categories.json +81 -0
- package/monol-plugin-scout-pkg/api/mock/insights.json +111 -0
- package/monol-plugin-scout-pkg/api/mock/marketplace.json +501 -0
- package/monol-plugin-scout-pkg/api/mock/trending.json +96 -0
- package/monol-plugin-scout-pkg/commands/console.md +79 -0
- package/monol-plugin-scout-pkg/commands/frequency.md +32 -0
- package/monol-plugin-scout-pkg/commands/install.md +32 -0
- package/monol-plugin-scout-pkg/commands/priority.md +30 -0
- package/monol-plugin-scout-pkg/commands/quiet.md +32 -0
- package/monol-plugin-scout-pkg/commands/schedule.md +32 -0
- package/monol-plugin-scout-pkg/commands/scout.md +8 -5
- package/monol-plugin-scout-pkg/commands/skills.md +160 -0
- package/monol-plugin-scout-pkg/commands/team.md +32 -0
- package/monol-plugin-scout-pkg/commands/timing.md +32 -0
- package/monol-plugin-scout-pkg/commands/trust.md +85 -0
- package/monol-plugin-scout-pkg/commands/trusted-install.md +98 -0
- package/monol-plugin-scout-pkg/commands/uninstall.md +31 -0
- package/monol-plugin-scout-pkg/config.yaml +57 -0
- package/monol-plugin-scout-pkg/data/.cache/676d5ab664292155e5f509c69d4edae1 +10 -0
- package/monol-plugin-scout-pkg/data/.session +8 -0
- package/monol-plugin-scout-pkg/data/analytics.json +102 -0
- package/monol-plugin-scout-pkg/data/history.json +67 -11
- package/monol-plugin-scout-pkg/data/logs/scout.log +1 -0
- package/monol-plugin-scout-pkg/data/schedules.json +17 -0
- package/monol-plugin-scout-pkg/data/team.json +53 -0
- package/monol-plugin-scout-pkg/data/usage.json +100 -7
- package/monol-plugin-scout-pkg/docs/ARCHITECTURE.md +201 -0
- package/monol-plugin-scout-pkg/hooks/generate-insights.sh +102 -0
- package/monol-plugin-scout-pkg/hooks/hooks.json +36 -1
- package/monol-plugin-scout-pkg/hooks/on-session-end.sh +136 -0
- package/monol-plugin-scout-pkg/hooks/on-session-start.sh +43 -0
- package/monol-plugin-scout-pkg/hooks/on-stop.md +79 -0
- package/monol-plugin-scout-pkg/hooks/open-console.sh +111 -0
- package/monol-plugin-scout-pkg/hooks/open-dashboard.sh +61 -0
- package/monol-plugin-scout-pkg/hooks/track-usage.sh +59 -0
- package/monol-plugin-scout-pkg/lib/ai-recommender.sh +505 -0
- package/monol-plugin-scout-pkg/lib/cache.sh +194 -0
- package/monol-plugin-scout-pkg/lib/data-validator.sh +360 -0
- package/monol-plugin-scout-pkg/lib/error-handler.sh +296 -0
- package/monol-plugin-scout-pkg/lib/logger.sh +263 -0
- package/monol-plugin-scout-pkg/lib/plugin-manager.sh +239 -0
- package/monol-plugin-scout-pkg/lib/priority-scorer.sh +262 -0
- package/monol-plugin-scout-pkg/lib/profile-learner.sh +339 -0
- package/monol-plugin-scout-pkg/lib/project-analyzer.sh +281 -0
- package/monol-plugin-scout-pkg/lib/recommendation-controller.sh +290 -0
- package/monol-plugin-scout-pkg/lib/rejection-learner.sh +232 -0
- package/monol-plugin-scout-pkg/lib/scheduler.sh +275 -0
- package/monol-plugin-scout-pkg/lib/skill-scout.sh +729 -0
- package/monol-plugin-scout-pkg/lib/sync.sh +221 -0
- package/monol-plugin-scout-pkg/lib/team-learner.sh +450 -0
- package/monol-plugin-scout-pkg/lib/team-manager.sh +369 -0
- package/monol-plugin-scout-pkg/lib/trend-learner.sh +428 -0
- package/monol-plugin-scout-pkg/lib/trust-manager.sh +261 -0
- package/monol-plugin-scout-pkg/lib/trusted-installer.sh +738 -0
- package/monol-plugin-scout-pkg/plugin.json +3 -2
- package/monol-plugin-scout-pkg/skills/audit.md +6 -0
- package/monol-plugin-scout-pkg/skills/cleanup.md +6 -0
- package/monol-plugin-scout-pkg/skills/compare.md +6 -0
- package/monol-plugin-scout-pkg/skills/console.md +315 -0
- package/monol-plugin-scout-pkg/skills/explore.md +6 -0
- package/monol-plugin-scout-pkg/skills/fork.md +6 -0
- package/monol-plugin-scout-pkg/skills/frequency.md +93 -0
- package/monol-plugin-scout-pkg/skills/install.md +127 -0
- package/monol-plugin-scout-pkg/skills/priority.md +77 -0
- package/monol-plugin-scout-pkg/skills/quiet.md +73 -0
- package/monol-plugin-scout-pkg/skills/schedule.md +95 -0
- package/monol-plugin-scout-pkg/skills/scout.md +27 -17
- package/monol-plugin-scout-pkg/skills/skills.md +230 -0
- package/monol-plugin-scout-pkg/skills/team.md +117 -0
- package/monol-plugin-scout-pkg/skills/timing.md +97 -0
- package/monol-plugin-scout-pkg/skills/trust.md +120 -0
- package/monol-plugin-scout-pkg/skills/trusted-install.md +264 -0
- package/monol-plugin-scout-pkg/skills/uninstall.md +100 -0
- package/monol-plugin-scout-pkg/web/components/activity-chart.js +208 -0
- package/monol-plugin-scout-pkg/web/components/index.js +27 -0
- package/monol-plugin-scout-pkg/web/components/insight-card.js +365 -0
- package/monol-plugin-scout-pkg/web/components/overview.js +154 -0
- package/monol-plugin-scout-pkg/web/components/plugin-list.js +242 -0
- package/monol-plugin-scout-pkg/web/components/stats-card.js +126 -0
- package/monol-plugin-scout-pkg/web/components/team-list.js +346 -0
- package/monol-plugin-scout-pkg/web/console.html +2098 -0
- package/monol-plugin-scout-pkg/web/dashboard.html +2106 -0
- package/monol-plugin-scout-pkg/web/manifest.json +29 -0
- package/package.json +1 -1
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Plugin Scout - AI 기반 추천 시스템
|
|
3
|
+
# 코드 구조 분석, 의존성 그래프 분석, 유사 프로젝트 참고 추천
|
|
4
|
+
|
|
5
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}"
|
|
6
|
+
DATA_DIR="$PLUGIN_ROOT/data"
|
|
7
|
+
AI_CACHE_FILE="$DATA_DIR/ai-cache.json"
|
|
8
|
+
PROJECT_SIGNATURES_FILE="$DATA_DIR/project-signatures.json"
|
|
9
|
+
|
|
10
|
+
# AI 캐시 초기화
|
|
11
|
+
init_ai_cache() {
|
|
12
|
+
if [ ! -f "$AI_CACHE_FILE" ]; then
|
|
13
|
+
mkdir -p "$DATA_DIR"
|
|
14
|
+
echo '{
|
|
15
|
+
"version": "1.0.0",
|
|
16
|
+
"lastUpdated": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'",
|
|
17
|
+
"projectAnalysis": {},
|
|
18
|
+
"dependencyGraph": {},
|
|
19
|
+
"recommendations": {},
|
|
20
|
+
"similarProjects": {}
|
|
21
|
+
}' > "$AI_CACHE_FILE"
|
|
22
|
+
fi
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# 프로젝트 시그니처 DB 초기화
|
|
26
|
+
init_signatures() {
|
|
27
|
+
if [ ! -f "$PROJECT_SIGNATURES_FILE" ]; then
|
|
28
|
+
mkdir -p "$DATA_DIR"
|
|
29
|
+
echo '{
|
|
30
|
+
"version": "1.0.0",
|
|
31
|
+
"signatures": {
|
|
32
|
+
"react-typescript": {
|
|
33
|
+
"markers": ["react", "typescript", "@types/react"],
|
|
34
|
+
"recommendedPlugins": ["eslint-react", "prettier-ts", "jest-react"]
|
|
35
|
+
},
|
|
36
|
+
"node-express": {
|
|
37
|
+
"markers": ["express", "node"],
|
|
38
|
+
"recommendedPlugins": ["nodemon", "pm2", "swagger-gen"]
|
|
39
|
+
},
|
|
40
|
+
"python-ml": {
|
|
41
|
+
"markers": ["numpy", "pandas", "scikit-learn", "tensorflow", "pytorch"],
|
|
42
|
+
"recommendedPlugins": ["jupyter", "mlflow", "tensorboard"]
|
|
43
|
+
},
|
|
44
|
+
"python-web": {
|
|
45
|
+
"markers": ["django", "flask", "fastapi"],
|
|
46
|
+
"recommendedPlugins": ["pytest", "black", "mypy"]
|
|
47
|
+
},
|
|
48
|
+
"rust-cli": {
|
|
49
|
+
"markers": ["clap", "tokio"],
|
|
50
|
+
"recommendedPlugins": ["cargo-watch", "cargo-audit"]
|
|
51
|
+
},
|
|
52
|
+
"go-web": {
|
|
53
|
+
"markers": ["gin", "echo", "fiber"],
|
|
54
|
+
"recommendedPlugins": ["air", "golangci-lint"]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}' > "$PROJECT_SIGNATURES_FILE"
|
|
58
|
+
fi
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# package.json 분석
|
|
62
|
+
analyze_package_json() {
|
|
63
|
+
local path="${1:-.}"
|
|
64
|
+
local pkg_file="$path/package.json"
|
|
65
|
+
|
|
66
|
+
if [ ! -f "$pkg_file" ]; then
|
|
67
|
+
echo "[]"
|
|
68
|
+
return
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
if ! command -v jq &> /dev/null; then
|
|
72
|
+
echo "[]"
|
|
73
|
+
return
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# 의존성 추출
|
|
77
|
+
jq -r '
|
|
78
|
+
((.dependencies // {}) + (.devDependencies // {})) |
|
|
79
|
+
keys
|
|
80
|
+
' "$pkg_file"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# requirements.txt 분석
|
|
84
|
+
analyze_requirements() {
|
|
85
|
+
local path="${1:-.}"
|
|
86
|
+
local req_file="$path/requirements.txt"
|
|
87
|
+
|
|
88
|
+
if [ ! -f "$req_file" ]; then
|
|
89
|
+
echo "[]"
|
|
90
|
+
return
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# 패키지 이름만 추출 (버전 제외)
|
|
94
|
+
grep -v '^#' "$req_file" 2>/dev/null | \
|
|
95
|
+
grep -v '^$' | \
|
|
96
|
+
sed 's/[>=<].*$//' | \
|
|
97
|
+
sed 's/\[.*\]$//' | \
|
|
98
|
+
jq -R -s -c 'split("\n") | map(select(. != ""))'
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Cargo.toml 분석
|
|
102
|
+
analyze_cargo() {
|
|
103
|
+
local path="${1:-.}"
|
|
104
|
+
local cargo_file="$path/Cargo.toml"
|
|
105
|
+
|
|
106
|
+
if [ ! -f "$cargo_file" ]; then
|
|
107
|
+
echo "[]"
|
|
108
|
+
return
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
# [dependencies] 섹션에서 패키지 추출
|
|
112
|
+
awk '/^\[dependencies\]/,/^\[/' "$cargo_file" | \
|
|
113
|
+
grep -v '^\[' | \
|
|
114
|
+
grep -v '^$' | \
|
|
115
|
+
sed 's/ =.*//' | \
|
|
116
|
+
jq -R -s -c 'split("\n") | map(select(. != ""))'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# go.mod 분석
|
|
120
|
+
analyze_gomod() {
|
|
121
|
+
local path="${1:-.}"
|
|
122
|
+
local go_file="$path/go.mod"
|
|
123
|
+
|
|
124
|
+
if [ ! -f "$go_file" ]; then
|
|
125
|
+
echo "[]"
|
|
126
|
+
return
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# require 블록에서 패키지 추출
|
|
130
|
+
awk '/^require/,/^\)/' "$go_file" | \
|
|
131
|
+
grep -v '^require' | \
|
|
132
|
+
grep -v '^\)' | \
|
|
133
|
+
awk '{print $1}' | \
|
|
134
|
+
grep -v '^$' | \
|
|
135
|
+
jq -R -s -c 'split("\n") | map(select(. != ""))'
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# 프로젝트 분석 (통합)
|
|
139
|
+
analyze_project() {
|
|
140
|
+
local path="${1:-.}"
|
|
141
|
+
|
|
142
|
+
init_ai_cache
|
|
143
|
+
|
|
144
|
+
local project_type=""
|
|
145
|
+
local dependencies="[]"
|
|
146
|
+
|
|
147
|
+
# 프로젝트 타입 감지 및 의존성 분석
|
|
148
|
+
if [ -f "$path/package.json" ]; then
|
|
149
|
+
project_type="node"
|
|
150
|
+
dependencies=$(analyze_package_json "$path")
|
|
151
|
+
elif [ -f "$path/requirements.txt" ] || [ -f "$path/pyproject.toml" ]; then
|
|
152
|
+
project_type="python"
|
|
153
|
+
dependencies=$(analyze_requirements "$path")
|
|
154
|
+
elif [ -f "$path/Cargo.toml" ]; then
|
|
155
|
+
project_type="rust"
|
|
156
|
+
dependencies=$(analyze_cargo "$path")
|
|
157
|
+
elif [ -f "$path/go.mod" ]; then
|
|
158
|
+
project_type="go"
|
|
159
|
+
dependencies=$(analyze_gomod "$path")
|
|
160
|
+
elif [ -f "$path/pom.xml" ]; then
|
|
161
|
+
project_type="java"
|
|
162
|
+
elif [ -f "$path/composer.json" ]; then
|
|
163
|
+
project_type="php"
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
if ! command -v jq &> /dev/null; then
|
|
167
|
+
echo "Project type: $project_type"
|
|
168
|
+
return
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
172
|
+
local path_hash=$(echo "$path" | md5 2>/dev/null || echo "$path" | md5sum | cut -d' ' -f1)
|
|
173
|
+
|
|
174
|
+
# 캐시에 저장
|
|
175
|
+
jq --arg path "$path_hash" \
|
|
176
|
+
--arg type "$project_type" \
|
|
177
|
+
--argjson deps "$dependencies" \
|
|
178
|
+
--arg ts "$timestamp" \
|
|
179
|
+
'
|
|
180
|
+
.lastUpdated = $ts |
|
|
181
|
+
.projectAnalysis[$path] = {
|
|
182
|
+
"type": $type,
|
|
183
|
+
"dependencies": $deps,
|
|
184
|
+
"analyzedAt": $ts
|
|
185
|
+
}
|
|
186
|
+
' "$AI_CACHE_FILE" > "$AI_CACHE_FILE.tmp" && mv "$AI_CACHE_FILE.tmp" "$AI_CACHE_FILE"
|
|
187
|
+
|
|
188
|
+
echo "{\"type\": \"$project_type\", \"dependencies\": $dependencies}"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# 의존성 그래프 구축
|
|
192
|
+
build_dependency_graph() {
|
|
193
|
+
local path="${1:-.}"
|
|
194
|
+
|
|
195
|
+
init_ai_cache
|
|
196
|
+
|
|
197
|
+
local analysis=$(analyze_project "$path")
|
|
198
|
+
|
|
199
|
+
if ! command -v jq &> /dev/null; then
|
|
200
|
+
echo "jq required"
|
|
201
|
+
return
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
local deps=$(echo "$analysis" | jq -r '.dependencies')
|
|
205
|
+
local project_type=$(echo "$analysis" | jq -r '.type')
|
|
206
|
+
|
|
207
|
+
# 간단한 의존성 그래프 (직접 의존성만)
|
|
208
|
+
local graph=$(echo "$deps" | jq '{
|
|
209
|
+
"nodes": . | map({id: ., type: "dependency"}),
|
|
210
|
+
"edges": []
|
|
211
|
+
}')
|
|
212
|
+
|
|
213
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
214
|
+
local path_hash=$(echo "$path" | md5 2>/dev/null || echo "$path" | md5sum | cut -d' ' -f1)
|
|
215
|
+
|
|
216
|
+
jq --arg path "$path_hash" \
|
|
217
|
+
--argjson graph "$graph" \
|
|
218
|
+
--arg ts "$timestamp" \
|
|
219
|
+
'
|
|
220
|
+
.lastUpdated = $ts |
|
|
221
|
+
.dependencyGraph[$path] = {
|
|
222
|
+
"graph": $graph,
|
|
223
|
+
"builtAt": $ts
|
|
224
|
+
}
|
|
225
|
+
' "$AI_CACHE_FILE" > "$AI_CACHE_FILE.tmp" && mv "$AI_CACHE_FILE.tmp" "$AI_CACHE_FILE"
|
|
226
|
+
|
|
227
|
+
echo "$graph"
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# 프로젝트 시그니처 매칭
|
|
231
|
+
match_signature() {
|
|
232
|
+
local path="${1:-.}"
|
|
233
|
+
|
|
234
|
+
init_signatures
|
|
235
|
+
|
|
236
|
+
local analysis=$(analyze_project "$path")
|
|
237
|
+
|
|
238
|
+
if ! command -v jq &> /dev/null; then
|
|
239
|
+
echo "[]"
|
|
240
|
+
return
|
|
241
|
+
fi
|
|
242
|
+
|
|
243
|
+
local deps=$(echo "$analysis" | jq -r '.dependencies | join(" ")')
|
|
244
|
+
|
|
245
|
+
# 시그니처별 매칭 점수 계산
|
|
246
|
+
jq -r --arg deps "$deps" '
|
|
247
|
+
.signatures | to_entries | map({
|
|
248
|
+
name: .key,
|
|
249
|
+
markers: .value.markers,
|
|
250
|
+
plugins: .value.recommendedPlugins,
|
|
251
|
+
matched: ([.value.markers[] | select($deps | contains(.))] | length),
|
|
252
|
+
total: (.value.markers | length)
|
|
253
|
+
}) |
|
|
254
|
+
map(select(.matched > 0)) |
|
|
255
|
+
sort_by(-.matched) |
|
|
256
|
+
.[0:3]
|
|
257
|
+
' "$PROJECT_SIGNATURES_FILE"
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
# AI 기반 추천 생성
|
|
261
|
+
generate_recommendations() {
|
|
262
|
+
local path="${1:-.}"
|
|
263
|
+
|
|
264
|
+
init_ai_cache
|
|
265
|
+
init_signatures
|
|
266
|
+
|
|
267
|
+
if ! command -v jq &> /dev/null; then
|
|
268
|
+
echo "jq required"
|
|
269
|
+
return
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
local analysis=$(analyze_project "$path")
|
|
273
|
+
local project_type=$(echo "$analysis" | jq -r '.type')
|
|
274
|
+
local deps=$(echo "$analysis" | jq -r '.dependencies')
|
|
275
|
+
|
|
276
|
+
# 시그니처 매칭
|
|
277
|
+
local matches=$(match_signature "$path")
|
|
278
|
+
|
|
279
|
+
# 추천 플러그인 추출
|
|
280
|
+
local recommendations=$(echo "$matches" | jq '
|
|
281
|
+
[.[].plugins] | flatten | unique
|
|
282
|
+
')
|
|
283
|
+
|
|
284
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
285
|
+
local path_hash=$(echo "$path" | md5 2>/dev/null || echo "$path" | md5sum | cut -d' ' -f1)
|
|
286
|
+
|
|
287
|
+
# 캐시에 저장
|
|
288
|
+
jq --arg path "$path_hash" \
|
|
289
|
+
--argjson recs "$recommendations" \
|
|
290
|
+
--argjson matches "$matches" \
|
|
291
|
+
--arg ts "$timestamp" \
|
|
292
|
+
'
|
|
293
|
+
.lastUpdated = $ts |
|
|
294
|
+
.recommendations[$path] = {
|
|
295
|
+
"plugins": $recs,
|
|
296
|
+
"matches": $matches,
|
|
297
|
+
"generatedAt": $ts
|
|
298
|
+
}
|
|
299
|
+
' "$AI_CACHE_FILE" > "$AI_CACHE_FILE.tmp" && mv "$AI_CACHE_FILE.tmp" "$AI_CACHE_FILE"
|
|
300
|
+
|
|
301
|
+
echo "$recommendations"
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
# 유사 프로젝트 찾기 (간단한 구현)
|
|
305
|
+
find_similar_projects() {
|
|
306
|
+
local path="${1:-.}"
|
|
307
|
+
|
|
308
|
+
init_ai_cache
|
|
309
|
+
|
|
310
|
+
if ! command -v jq &> /dev/null; then
|
|
311
|
+
echo "[]"
|
|
312
|
+
return
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
local analysis=$(analyze_project "$path")
|
|
316
|
+
local deps=$(echo "$analysis" | jq -r '.dependencies')
|
|
317
|
+
local project_type=$(echo "$analysis" | jq -r '.type')
|
|
318
|
+
|
|
319
|
+
# 캐시된 다른 프로젝트와 비교
|
|
320
|
+
local path_hash=$(echo "$path" | md5 2>/dev/null || echo "$path" | md5sum | cut -d' ' -f1)
|
|
321
|
+
|
|
322
|
+
jq --arg current "$path_hash" --argjson currentDeps "$deps" '
|
|
323
|
+
.projectAnalysis | to_entries |
|
|
324
|
+
map(select(.key != $current)) |
|
|
325
|
+
map({
|
|
326
|
+
path: .key,
|
|
327
|
+
type: .value.type,
|
|
328
|
+
commonDeps: ([.value.dependencies[] | select(. as $d | $currentDeps | index($d))] | length),
|
|
329
|
+
totalDeps: (.value.dependencies | length)
|
|
330
|
+
}) |
|
|
331
|
+
map(select(.commonDeps > 0)) |
|
|
332
|
+
sort_by(-.commonDeps) |
|
|
333
|
+
.[0:5]
|
|
334
|
+
' "$AI_CACHE_FILE"
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
# 코드 패턴 분석 (기본적인 파일 구조 분석)
|
|
338
|
+
analyze_code_patterns() {
|
|
339
|
+
local path="${1:-.}"
|
|
340
|
+
|
|
341
|
+
local patterns="[]"
|
|
342
|
+
|
|
343
|
+
# 디렉토리 구조 분석
|
|
344
|
+
if [ -d "$path/src" ]; then
|
|
345
|
+
patterns=$(echo "$patterns" | jq '. + ["src-structure"]')
|
|
346
|
+
fi
|
|
347
|
+
|
|
348
|
+
if [ -d "$path/test" ] || [ -d "$path/tests" ] || [ -d "$path/__tests__" ]; then
|
|
349
|
+
patterns=$(echo "$patterns" | jq '. + ["has-tests"]')
|
|
350
|
+
fi
|
|
351
|
+
|
|
352
|
+
if [ -d "$path/.github" ]; then
|
|
353
|
+
patterns=$(echo "$patterns" | jq '. + ["github-workflows"]')
|
|
354
|
+
fi
|
|
355
|
+
|
|
356
|
+
if [ -f "$path/.eslintrc.json" ] || [ -f "$path/.eslintrc.js" ] || [ -f "$path/eslint.config.js" ]; then
|
|
357
|
+
patterns=$(echo "$patterns" | jq '. + ["eslint-config"]')
|
|
358
|
+
fi
|
|
359
|
+
|
|
360
|
+
if [ -f "$path/.prettierrc" ] || [ -f "$path/prettier.config.js" ]; then
|
|
361
|
+
patterns=$(echo "$patterns" | jq '. + ["prettier-config"]')
|
|
362
|
+
fi
|
|
363
|
+
|
|
364
|
+
if [ -f "$path/docker-compose.yml" ] || [ -f "$path/Dockerfile" ]; then
|
|
365
|
+
patterns=$(echo "$patterns" | jq '. + ["docker-enabled"]')
|
|
366
|
+
fi
|
|
367
|
+
|
|
368
|
+
echo "$patterns"
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
# 추천 점수 계산
|
|
372
|
+
calculate_recommendation_score() {
|
|
373
|
+
local plugin_name="$1"
|
|
374
|
+
local path="${2:-.}"
|
|
375
|
+
|
|
376
|
+
local base_score=50
|
|
377
|
+
|
|
378
|
+
# 시그니처 매칭 보너스
|
|
379
|
+
local matches=$(match_signature "$path")
|
|
380
|
+
local in_match=$(echo "$matches" | jq -r --arg plugin "$plugin_name" '
|
|
381
|
+
[.[].plugins[] | select(. == $plugin)] | length
|
|
382
|
+
')
|
|
383
|
+
|
|
384
|
+
if [ "$in_match" != "0" ]; then
|
|
385
|
+
base_score=$((base_score + 30))
|
|
386
|
+
fi
|
|
387
|
+
|
|
388
|
+
# 코드 패턴 보너스 (예: 테스트가 있으면 테스트 플러그인 점수 상승)
|
|
389
|
+
local patterns=$(analyze_code_patterns "$path")
|
|
390
|
+
|
|
391
|
+
echo "$base_score"
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
# 캐시 정리
|
|
395
|
+
cleanup_cache() {
|
|
396
|
+
local days="${1:-7}"
|
|
397
|
+
|
|
398
|
+
init_ai_cache
|
|
399
|
+
|
|
400
|
+
if ! command -v jq &> /dev/null; then
|
|
401
|
+
echo "jq required"
|
|
402
|
+
return
|
|
403
|
+
fi
|
|
404
|
+
|
|
405
|
+
local cutoff=$(date -u -v-${days}d +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -d "$days days ago" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null)
|
|
406
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
407
|
+
|
|
408
|
+
jq --arg cutoff "$cutoff" --arg ts "$timestamp" '
|
|
409
|
+
.lastUpdated = $ts |
|
|
410
|
+
.projectAnalysis = (.projectAnalysis | to_entries | map(select(.value.analyzedAt >= $cutoff)) | from_entries) |
|
|
411
|
+
.recommendations = (.recommendations | to_entries | map(select(.value.generatedAt >= $cutoff)) | from_entries)
|
|
412
|
+
' "$AI_CACHE_FILE" > "$AI_CACHE_FILE.tmp" && mv "$AI_CACHE_FILE.tmp" "$AI_CACHE_FILE"
|
|
413
|
+
|
|
414
|
+
echo "Cache cleaned (removed entries older than $days days)"
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
# AI 추천 요약 출력
|
|
418
|
+
get_ai_summary() {
|
|
419
|
+
local path="${1:-.}"
|
|
420
|
+
|
|
421
|
+
echo "=== AI Recommendation Summary ==="
|
|
422
|
+
echo ""
|
|
423
|
+
|
|
424
|
+
local analysis=$(analyze_project "$path")
|
|
425
|
+
echo "Project Type: $(echo "$analysis" | jq -r '.type // "unknown"')"
|
|
426
|
+
echo "Dependencies: $(echo "$analysis" | jq -r '.dependencies | length') packages"
|
|
427
|
+
echo ""
|
|
428
|
+
|
|
429
|
+
echo "Signature Matches:"
|
|
430
|
+
match_signature "$path" | jq -r '.[] | " \(.name): \(.matched)/\(.total) markers"'
|
|
431
|
+
echo ""
|
|
432
|
+
|
|
433
|
+
echo "Recommended Plugins:"
|
|
434
|
+
generate_recommendations "$path" | jq -r '.[] | " - \(.)"'
|
|
435
|
+
echo ""
|
|
436
|
+
|
|
437
|
+
echo "Code Patterns:"
|
|
438
|
+
analyze_code_patterns "$path" | jq -r '.[] | " - \(.)"'
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
# 캐시 리셋
|
|
442
|
+
reset_cache() {
|
|
443
|
+
if [ -f "$AI_CACHE_FILE" ]; then
|
|
444
|
+
rm "$AI_CACHE_FILE"
|
|
445
|
+
init_ai_cache
|
|
446
|
+
echo "AI cache reset successfully"
|
|
447
|
+
else
|
|
448
|
+
echo "No cache to reset"
|
|
449
|
+
fi
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
# CLI 인터페이스
|
|
453
|
+
case "$1" in
|
|
454
|
+
init)
|
|
455
|
+
init_ai_cache
|
|
456
|
+
init_signatures
|
|
457
|
+
echo "AI recommender initialized"
|
|
458
|
+
;;
|
|
459
|
+
analyze)
|
|
460
|
+
analyze_project "$2"
|
|
461
|
+
;;
|
|
462
|
+
graph)
|
|
463
|
+
build_dependency_graph "$2"
|
|
464
|
+
;;
|
|
465
|
+
match)
|
|
466
|
+
match_signature "$2"
|
|
467
|
+
;;
|
|
468
|
+
recommend)
|
|
469
|
+
generate_recommendations "$2"
|
|
470
|
+
;;
|
|
471
|
+
similar)
|
|
472
|
+
find_similar_projects "$2"
|
|
473
|
+
;;
|
|
474
|
+
patterns)
|
|
475
|
+
analyze_code_patterns "$2"
|
|
476
|
+
;;
|
|
477
|
+
score)
|
|
478
|
+
calculate_recommendation_score "$2" "$3"
|
|
479
|
+
;;
|
|
480
|
+
cleanup)
|
|
481
|
+
cleanup_cache "$2"
|
|
482
|
+
;;
|
|
483
|
+
summary)
|
|
484
|
+
get_ai_summary "$2"
|
|
485
|
+
;;
|
|
486
|
+
reset)
|
|
487
|
+
reset_cache
|
|
488
|
+
;;
|
|
489
|
+
*)
|
|
490
|
+
echo "Usage: $0 {init|analyze|graph|match|recommend|similar|patterns|score|cleanup|summary|reset}"
|
|
491
|
+
echo ""
|
|
492
|
+
echo "Commands:"
|
|
493
|
+
echo " init - Initialize AI recommender"
|
|
494
|
+
echo " analyze [path] - Analyze project dependencies"
|
|
495
|
+
echo " graph [path] - Build dependency graph"
|
|
496
|
+
echo " match [path] - Match project signature"
|
|
497
|
+
echo " recommend [path] - Generate AI recommendations"
|
|
498
|
+
echo " similar [path] - Find similar projects"
|
|
499
|
+
echo " patterns [path] - Analyze code patterns"
|
|
500
|
+
echo " score <plugin> [path] - Calculate recommendation score"
|
|
501
|
+
echo " cleanup [days] - Clean old cache entries"
|
|
502
|
+
echo " summary [path] - Show AI recommendation summary"
|
|
503
|
+
echo " reset - Reset AI cache"
|
|
504
|
+
;;
|
|
505
|
+
esac
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Plugin Scout - 캐싱 유틸리티
|
|
3
|
+
# 자주 사용하는 데이터를 메모리/파일에 캐싱
|
|
4
|
+
|
|
5
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}"
|
|
6
|
+
DATA_DIR="$PLUGIN_ROOT/data"
|
|
7
|
+
CACHE_DIR="$DATA_DIR/.cache"
|
|
8
|
+
CACHE_TTL="${SCOUT_CACHE_TTL:-300}" # 기본 5분
|
|
9
|
+
|
|
10
|
+
# 캐시 디렉토리 생성
|
|
11
|
+
mkdir -p "$CACHE_DIR"
|
|
12
|
+
|
|
13
|
+
# 캐시 키 해시 생성
|
|
14
|
+
get_cache_key() {
|
|
15
|
+
local input="$1"
|
|
16
|
+
echo "$input" | md5 2>/dev/null || echo "$input" | md5sum 2>/dev/null | cut -d' ' -f1
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# 캐시 파일 경로
|
|
20
|
+
get_cache_path() {
|
|
21
|
+
local key="$1"
|
|
22
|
+
local hash=$(get_cache_key "$key")
|
|
23
|
+
echo "$CACHE_DIR/$hash"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# 캐시에서 읽기
|
|
27
|
+
cache_get() {
|
|
28
|
+
local key="$1"
|
|
29
|
+
local ttl="${2:-$CACHE_TTL}"
|
|
30
|
+
local cache_file=$(get_cache_path "$key")
|
|
31
|
+
|
|
32
|
+
# 캐시 파일 존재 확인
|
|
33
|
+
if [ ! -f "$cache_file" ]; then
|
|
34
|
+
return 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# TTL 확인
|
|
38
|
+
local file_age=$(( $(date +%s) - $(stat -f%m "$cache_file" 2>/dev/null || stat -c%Y "$cache_file" 2>/dev/null) ))
|
|
39
|
+
if [ "$file_age" -gt "$ttl" ]; then
|
|
40
|
+
rm -f "$cache_file"
|
|
41
|
+
return 1
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
cat "$cache_file"
|
|
45
|
+
return 0
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# 캐시에 저장
|
|
49
|
+
cache_set() {
|
|
50
|
+
local key="$1"
|
|
51
|
+
local value="$2"
|
|
52
|
+
local cache_file=$(get_cache_path "$key")
|
|
53
|
+
|
|
54
|
+
echo "$value" > "$cache_file"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# 캐시 삭제
|
|
58
|
+
cache_delete() {
|
|
59
|
+
local key="$1"
|
|
60
|
+
local cache_file=$(get_cache_path "$key")
|
|
61
|
+
|
|
62
|
+
rm -f "$cache_file"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# 모든 캐시 삭제
|
|
66
|
+
cache_clear() {
|
|
67
|
+
rm -f "$CACHE_DIR"/*
|
|
68
|
+
echo "Cache cleared"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# 캐시 통계
|
|
72
|
+
cache_stats() {
|
|
73
|
+
echo "=== Cache Statistics ==="
|
|
74
|
+
echo ""
|
|
75
|
+
echo "Cache directory: $CACHE_DIR"
|
|
76
|
+
echo "Cache TTL: ${CACHE_TTL}s"
|
|
77
|
+
echo ""
|
|
78
|
+
|
|
79
|
+
if [ -d "$CACHE_DIR" ]; then
|
|
80
|
+
local count=$(ls -1 "$CACHE_DIR" 2>/dev/null | wc -l | tr -d ' ')
|
|
81
|
+
local size=$(du -sh "$CACHE_DIR" 2>/dev/null | cut -f1)
|
|
82
|
+
echo "Cached items: $count"
|
|
83
|
+
echo "Total size: ${size:-0}"
|
|
84
|
+
else
|
|
85
|
+
echo "Cache directory not found"
|
|
86
|
+
fi
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# 캐시된 함수 실행 (결과 캐싱)
|
|
90
|
+
cached_exec() {
|
|
91
|
+
local key="$1"
|
|
92
|
+
shift
|
|
93
|
+
local command="$@"
|
|
94
|
+
local ttl="${SCOUT_CACHE_TTL:-300}"
|
|
95
|
+
|
|
96
|
+
# 캐시 확인
|
|
97
|
+
local cached=$(cache_get "$key" "$ttl")
|
|
98
|
+
if [ -n "$cached" ]; then
|
|
99
|
+
echo "$cached"
|
|
100
|
+
return 0
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
# 명령 실행 및 캐싱
|
|
104
|
+
local result
|
|
105
|
+
result=$(eval "$command")
|
|
106
|
+
local exit_code=$?
|
|
107
|
+
|
|
108
|
+
if [ $exit_code -eq 0 ]; then
|
|
109
|
+
cache_set "$key" "$result"
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
echo "$result"
|
|
113
|
+
return $exit_code
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# 프로젝트 분석 캐싱
|
|
117
|
+
cached_project_analysis() {
|
|
118
|
+
local key="project_analysis_$(pwd | get_cache_key)"
|
|
119
|
+
|
|
120
|
+
cached_exec "$key" "bash '$PLUGIN_ROOT/lib/project-analyzer.sh' full"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# 팀 추천 캐싱
|
|
124
|
+
cached_team_recommendations() {
|
|
125
|
+
local key="team_recommendations"
|
|
126
|
+
|
|
127
|
+
cached_exec "$key" "bash '$PLUGIN_ROOT/lib/team-manager.sh' recommendations"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# 만료된 캐시 정리
|
|
131
|
+
cache_cleanup() {
|
|
132
|
+
local ttl="${1:-$CACHE_TTL}"
|
|
133
|
+
local now=$(date +%s)
|
|
134
|
+
local count=0
|
|
135
|
+
|
|
136
|
+
for file in "$CACHE_DIR"/*; do
|
|
137
|
+
if [ -f "$file" ]; then
|
|
138
|
+
local file_time=$(stat -f%m "$file" 2>/dev/null || stat -c%Y "$file" 2>/dev/null)
|
|
139
|
+
local age=$(( now - file_time ))
|
|
140
|
+
if [ "$age" -gt "$ttl" ]; then
|
|
141
|
+
rm -f "$file"
|
|
142
|
+
count=$((count + 1))
|
|
143
|
+
fi
|
|
144
|
+
fi
|
|
145
|
+
done
|
|
146
|
+
|
|
147
|
+
echo "Cleaned $count expired cache entries"
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
# CLI 인터페이스
|
|
151
|
+
case "$1" in
|
|
152
|
+
get)
|
|
153
|
+
cache_get "$2" "$3"
|
|
154
|
+
;;
|
|
155
|
+
set)
|
|
156
|
+
cache_set "$2" "$3"
|
|
157
|
+
;;
|
|
158
|
+
delete)
|
|
159
|
+
cache_delete "$2"
|
|
160
|
+
;;
|
|
161
|
+
clear)
|
|
162
|
+
cache_clear
|
|
163
|
+
;;
|
|
164
|
+
stats)
|
|
165
|
+
cache_stats
|
|
166
|
+
;;
|
|
167
|
+
cleanup)
|
|
168
|
+
cache_cleanup "$2"
|
|
169
|
+
;;
|
|
170
|
+
project)
|
|
171
|
+
cached_project_analysis
|
|
172
|
+
;;
|
|
173
|
+
team)
|
|
174
|
+
cached_team_recommendations
|
|
175
|
+
;;
|
|
176
|
+
exec)
|
|
177
|
+
shift
|
|
178
|
+
cached_exec "$@"
|
|
179
|
+
;;
|
|
180
|
+
*)
|
|
181
|
+
echo "Usage: $0 {get|set|delete|clear|stats|cleanup|project|team|exec}"
|
|
182
|
+
echo ""
|
|
183
|
+
echo "Commands:"
|
|
184
|
+
echo " get <key> [ttl] - Get cached value"
|
|
185
|
+
echo " set <key> <value> - Set cache value"
|
|
186
|
+
echo " delete <key> - Delete cache entry"
|
|
187
|
+
echo " clear - Clear all cache"
|
|
188
|
+
echo " stats - Show cache statistics"
|
|
189
|
+
echo " cleanup [ttl] - Clean expired entries"
|
|
190
|
+
echo " project - Cached project analysis"
|
|
191
|
+
echo " team - Cached team recommendations"
|
|
192
|
+
echo " exec <key> <cmd> - Execute with caching"
|
|
193
|
+
;;
|
|
194
|
+
esac
|