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,262 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Plugin Scout - 우선순위 스코어링 시스템
|
|
3
|
+
# 다양한 요소를 기반으로 플러그인 우선순위 계산
|
|
4
|
+
|
|
5
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}"
|
|
6
|
+
DATA_DIR="$PLUGIN_ROOT/data"
|
|
7
|
+
HISTORY_FILE="$DATA_DIR/history.json"
|
|
8
|
+
USAGE_FILE="$DATA_DIR/usage.json"
|
|
9
|
+
TEAM_FILE="$DATA_DIR/team.json"
|
|
10
|
+
|
|
11
|
+
# 기본 가중치 설정
|
|
12
|
+
WEIGHT_PROJECT_MATCH=40
|
|
13
|
+
WEIGHT_TEAM_POPULARITY=25
|
|
14
|
+
WEIGHT_PERSONAL_HISTORY=20
|
|
15
|
+
WEIGHT_FRESHNESS=15
|
|
16
|
+
|
|
17
|
+
# 프로젝트 매칭 점수 계산
|
|
18
|
+
calc_project_match_score() {
|
|
19
|
+
local plugin="$1"
|
|
20
|
+
local project_types="$2"
|
|
21
|
+
local frameworks="$3"
|
|
22
|
+
|
|
23
|
+
# 간단한 매칭 로직 (실제로는 더 정교하게)
|
|
24
|
+
local score=50 # 기본 점수
|
|
25
|
+
|
|
26
|
+
# 플러그인-프레임워크 매핑
|
|
27
|
+
case "$plugin" in
|
|
28
|
+
*react*) echo "$frameworks" | grep -q "react" && score=90 ;;
|
|
29
|
+
*typescript*|*ts*) echo "$project_types" | grep -q "typescript" && score=90 ;;
|
|
30
|
+
*eslint*) echo "$frameworks" | grep -q "eslint" && score=85 ;;
|
|
31
|
+
*jest*|*vitest*|*test*) echo "$frameworks" | grep -qE "(jest|vitest|playwright)" && score=85 ;;
|
|
32
|
+
*docker*) echo "$project_types" | grep -q "docker" && score=80 ;;
|
|
33
|
+
*prisma*) echo "$frameworks" | grep -q "prisma" && score=85 ;;
|
|
34
|
+
*python*|*py*) echo "$project_types" | grep -q "python" && score=80 ;;
|
|
35
|
+
*) score=50 ;;
|
|
36
|
+
esac
|
|
37
|
+
|
|
38
|
+
echo "$score"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# 팀 인기도 점수 계산
|
|
42
|
+
calc_team_popularity_score() {
|
|
43
|
+
local plugin="$1"
|
|
44
|
+
|
|
45
|
+
if [ ! -f "$TEAM_FILE" ] || ! command -v jq &> /dev/null; then
|
|
46
|
+
echo "50" # 기본 점수
|
|
47
|
+
return
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# 팀 내 사용자 수 확인
|
|
51
|
+
local team_users=$(jq -r --arg p "$plugin" '
|
|
52
|
+
[.members | to_entries[] | select(.value.pluginsUsed | index($p))] | length
|
|
53
|
+
' "$TEAM_FILE" 2>/dev/null || echo "0")
|
|
54
|
+
|
|
55
|
+
local total_members=$(jq -r '.stats.totalMembers // 1' "$TEAM_FILE" 2>/dev/null || echo "1")
|
|
56
|
+
|
|
57
|
+
# 팀 추천 확인
|
|
58
|
+
local is_shared=$(jq -r --arg p "$plugin" '
|
|
59
|
+
if (.sharedRecommendations | map(.plugin) | index($p)) != null then "1" else "0" end
|
|
60
|
+
' "$TEAM_FILE" 2>/dev/null || echo "0")
|
|
61
|
+
|
|
62
|
+
# 점수 계산
|
|
63
|
+
local score=50
|
|
64
|
+
if [ "$total_members" -gt 0 ]; then
|
|
65
|
+
local usage_rate=$(( team_users * 100 / total_members ))
|
|
66
|
+
score=$(( 50 + usage_rate / 2 ))
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# 팀 추천이면 보너스
|
|
70
|
+
if [ "$is_shared" = "1" ]; then
|
|
71
|
+
score=$(( score + 15 ))
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# 0-100 범위로 제한
|
|
75
|
+
[ "$score" -gt 100 ] && score=100
|
|
76
|
+
[ "$score" -lt 0 ] && score=0
|
|
77
|
+
|
|
78
|
+
echo "$score"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# 개인 이력 점수 계산
|
|
82
|
+
calc_personal_history_score() {
|
|
83
|
+
local plugin="$1"
|
|
84
|
+
|
|
85
|
+
if [ ! -f "$HISTORY_FILE" ] || ! command -v jq &> /dev/null; then
|
|
86
|
+
echo "50"
|
|
87
|
+
return
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# 거절 여부 확인
|
|
91
|
+
local declined_count=$(jq -r --arg p "$plugin" '.declined[$p].count // 0' "$HISTORY_FILE" 2>/dev/null || echo "0")
|
|
92
|
+
|
|
93
|
+
# 이전 사용 여부 확인
|
|
94
|
+
local was_installed=$(jq -r --arg p "$plugin" '
|
|
95
|
+
if .installed[$p] then "1" else "0" end
|
|
96
|
+
' "$HISTORY_FILE" 2>/dev/null || echo "0")
|
|
97
|
+
|
|
98
|
+
# 점수 계산
|
|
99
|
+
local score=50
|
|
100
|
+
|
|
101
|
+
# 거절 이력이 있으면 감점
|
|
102
|
+
if [ "$declined_count" -gt 0 ]; then
|
|
103
|
+
score=$(( 50 - declined_count * 20 ))
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# 이전에 설치했었다면 가점
|
|
107
|
+
if [ "$was_installed" = "1" ]; then
|
|
108
|
+
score=$(( score + 20 ))
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
# 0-100 범위로 제한
|
|
112
|
+
[ "$score" -gt 100 ] && score=100
|
|
113
|
+
[ "$score" -lt 0 ] && score=0
|
|
114
|
+
|
|
115
|
+
echo "$score"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# 신선도 점수 계산 (최근 업데이트)
|
|
119
|
+
calc_freshness_score() {
|
|
120
|
+
local plugin="$1"
|
|
121
|
+
local last_update="$2" # ISO 8601 날짜
|
|
122
|
+
|
|
123
|
+
if [ -z "$last_update" ]; then
|
|
124
|
+
echo "50"
|
|
125
|
+
return
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
# 현재 시간과 비교 (일 단위)
|
|
129
|
+
local now=$(date +%s)
|
|
130
|
+
local update_ts=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$last_update" +%s 2>/dev/null || date -d "$last_update" +%s 2>/dev/null)
|
|
131
|
+
|
|
132
|
+
if [ -z "$update_ts" ]; then
|
|
133
|
+
echo "50"
|
|
134
|
+
return
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
local days_old=$(( (now - update_ts) / 86400 ))
|
|
138
|
+
|
|
139
|
+
# 최근 업데이트일수록 높은 점수
|
|
140
|
+
local score
|
|
141
|
+
if [ "$days_old" -lt 7 ]; then
|
|
142
|
+
score=95
|
|
143
|
+
elif [ "$days_old" -lt 30 ]; then
|
|
144
|
+
score=85
|
|
145
|
+
elif [ "$days_old" -lt 90 ]; then
|
|
146
|
+
score=70
|
|
147
|
+
elif [ "$days_old" -lt 180 ]; then
|
|
148
|
+
score=55
|
|
149
|
+
else
|
|
150
|
+
score=40
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
echo "$score"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# 종합 점수 계산
|
|
157
|
+
calc_total_score() {
|
|
158
|
+
local plugin="$1"
|
|
159
|
+
local project_types="${2:-}"
|
|
160
|
+
local frameworks="${3:-}"
|
|
161
|
+
local last_update="${4:-}"
|
|
162
|
+
|
|
163
|
+
# 각 요소별 점수 계산
|
|
164
|
+
local project_score=$(calc_project_match_score "$plugin" "$project_types" "$frameworks")
|
|
165
|
+
local team_score=$(calc_team_popularity_score "$plugin")
|
|
166
|
+
local history_score=$(calc_personal_history_score "$plugin")
|
|
167
|
+
local fresh_score=$(calc_freshness_score "$plugin" "$last_update")
|
|
168
|
+
|
|
169
|
+
# 가중 평균 계산
|
|
170
|
+
local total=$((
|
|
171
|
+
project_score * WEIGHT_PROJECT_MATCH / 100 +
|
|
172
|
+
team_score * WEIGHT_TEAM_POPULARITY / 100 +
|
|
173
|
+
history_score * WEIGHT_PERSONAL_HISTORY / 100 +
|
|
174
|
+
fresh_score * WEIGHT_FRESHNESS / 100
|
|
175
|
+
))
|
|
176
|
+
|
|
177
|
+
echo "$total"
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# 점수 상세 분석
|
|
181
|
+
score_breakdown() {
|
|
182
|
+
local plugin="$1"
|
|
183
|
+
local project_types="${2:-}"
|
|
184
|
+
local frameworks="${3:-}"
|
|
185
|
+
local last_update="${4:-}"
|
|
186
|
+
|
|
187
|
+
local project_score=$(calc_project_match_score "$plugin" "$project_types" "$frameworks")
|
|
188
|
+
local team_score=$(calc_team_popularity_score "$plugin")
|
|
189
|
+
local history_score=$(calc_personal_history_score "$plugin")
|
|
190
|
+
local fresh_score=$(calc_freshness_score "$plugin" "$last_update")
|
|
191
|
+
local total=$(calc_total_score "$plugin" "$project_types" "$frameworks" "$last_update")
|
|
192
|
+
|
|
193
|
+
echo "=== Score Breakdown: $plugin ==="
|
|
194
|
+
echo ""
|
|
195
|
+
printf "%-20s %3d (weight: %d%%)\n" "Project Match:" "$project_score" "$WEIGHT_PROJECT_MATCH"
|
|
196
|
+
printf "%-20s %3d (weight: %d%%)\n" "Team Popularity:" "$team_score" "$WEIGHT_TEAM_POPULARITY"
|
|
197
|
+
printf "%-20s %3d (weight: %d%%)\n" "Personal History:" "$history_score" "$WEIGHT_PERSONAL_HISTORY"
|
|
198
|
+
printf "%-20s %3d (weight: %d%%)\n" "Freshness:" "$fresh_score" "$WEIGHT_FRESHNESS"
|
|
199
|
+
echo "---"
|
|
200
|
+
printf "%-20s %3d\n" "TOTAL:" "$total"
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
# 플러그인 목록 정렬
|
|
204
|
+
sort_by_score() {
|
|
205
|
+
local plugins_json="$1"
|
|
206
|
+
local project_types="${2:-}"
|
|
207
|
+
local frameworks="${3:-}"
|
|
208
|
+
|
|
209
|
+
if [ -z "$plugins_json" ] || ! command -v jq &> /dev/null; then
|
|
210
|
+
echo "$plugins_json"
|
|
211
|
+
return
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
# 각 플러그인에 점수 추가 후 정렬
|
|
215
|
+
echo "$plugins_json" | jq -r '.[]' | while read plugin; do
|
|
216
|
+
local score=$(calc_total_score "$plugin" "$project_types" "$frameworks")
|
|
217
|
+
echo "$score $plugin"
|
|
218
|
+
done | sort -rn | awk '{print $2}' | jq -R -s -c 'split("\n") | map(select(. != ""))'
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
# CLI 인터페이스
|
|
222
|
+
case "$1" in
|
|
223
|
+
score)
|
|
224
|
+
calc_total_score "$2" "$3" "$4" "$5"
|
|
225
|
+
;;
|
|
226
|
+
breakdown)
|
|
227
|
+
score_breakdown "$2" "$3" "$4" "$5"
|
|
228
|
+
;;
|
|
229
|
+
project)
|
|
230
|
+
calc_project_match_score "$2" "$3" "$4"
|
|
231
|
+
;;
|
|
232
|
+
team)
|
|
233
|
+
calc_team_popularity_score "$2"
|
|
234
|
+
;;
|
|
235
|
+
history)
|
|
236
|
+
calc_personal_history_score "$2"
|
|
237
|
+
;;
|
|
238
|
+
fresh)
|
|
239
|
+
calc_freshness_score "$2" "$3"
|
|
240
|
+
;;
|
|
241
|
+
sort)
|
|
242
|
+
sort_by_score "$2" "$3" "$4"
|
|
243
|
+
;;
|
|
244
|
+
*)
|
|
245
|
+
echo "Usage: $0 {score|breakdown|project|team|history|fresh|sort}"
|
|
246
|
+
echo ""
|
|
247
|
+
echo "Commands:"
|
|
248
|
+
echo " score <plugin> [types] [frameworks] [update] - Calculate total score"
|
|
249
|
+
echo " breakdown <plugin> [...] - Show score breakdown"
|
|
250
|
+
echo " project <plugin> <types> <frameworks> - Project match score"
|
|
251
|
+
echo " team <plugin> - Team popularity score"
|
|
252
|
+
echo " history <plugin> - Personal history score"
|
|
253
|
+
echo " fresh <plugin> <last_update> - Freshness score"
|
|
254
|
+
echo " sort <plugins_json> [types] [frameworks] - Sort plugins by score"
|
|
255
|
+
echo ""
|
|
256
|
+
echo "Score Weights:"
|
|
257
|
+
echo " Project Match: $WEIGHT_PROJECT_MATCH%"
|
|
258
|
+
echo " Team Popularity: $WEIGHT_TEAM_POPULARITY%"
|
|
259
|
+
echo " Personal History: $WEIGHT_PERSONAL_HISTORY%"
|
|
260
|
+
echo " Freshness: $WEIGHT_FRESHNESS%"
|
|
261
|
+
;;
|
|
262
|
+
esac
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Plugin Scout - 프로필 학습 시스템
|
|
3
|
+
# 사용자 선호도 및 활동 패턴을 학습하여 개인화된 추천 제공
|
|
4
|
+
|
|
5
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}"
|
|
6
|
+
DATA_DIR="$PLUGIN_ROOT/data"
|
|
7
|
+
PROFILE_FILE="$DATA_DIR/profile.json"
|
|
8
|
+
|
|
9
|
+
# 프로필 초기화
|
|
10
|
+
init_profile() {
|
|
11
|
+
if [ ! -f "$PROFILE_FILE" ]; then
|
|
12
|
+
mkdir -p "$DATA_DIR"
|
|
13
|
+
echo '{
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"lastUpdated": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'",
|
|
16
|
+
"preferences": {
|
|
17
|
+
"categories": {},
|
|
18
|
+
"projectTypes": {},
|
|
19
|
+
"tags": {}
|
|
20
|
+
},
|
|
21
|
+
"activityPatterns": {
|
|
22
|
+
"hourly": {},
|
|
23
|
+
"daily": {},
|
|
24
|
+
"weekly": {}
|
|
25
|
+
},
|
|
26
|
+
"installHistory": [],
|
|
27
|
+
"usageStats": {
|
|
28
|
+
"totalInstalls": 0,
|
|
29
|
+
"totalRecommendations": 0,
|
|
30
|
+
"acceptRate": 0
|
|
31
|
+
}
|
|
32
|
+
}' > "$PROFILE_FILE"
|
|
33
|
+
fi
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# 카테고리 선호도 업데이트
|
|
37
|
+
update_category_preference() {
|
|
38
|
+
local category="$1"
|
|
39
|
+
local action="$2" # accept, reject, install, uninstall
|
|
40
|
+
local weight=1
|
|
41
|
+
|
|
42
|
+
case "$action" in
|
|
43
|
+
accept) weight=2 ;;
|
|
44
|
+
install) weight=5 ;;
|
|
45
|
+
reject) weight=-1 ;;
|
|
46
|
+
uninstall) weight=-3 ;;
|
|
47
|
+
esac
|
|
48
|
+
|
|
49
|
+
init_profile
|
|
50
|
+
|
|
51
|
+
if command -v jq &> /dev/null; then
|
|
52
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
53
|
+
local current=$(jq -r ".preferences.categories[\"$category\"] // 0" "$PROFILE_FILE")
|
|
54
|
+
local new_score=$((current + weight))
|
|
55
|
+
|
|
56
|
+
# 점수는 -100 ~ 100 범위로 제한
|
|
57
|
+
if [ "$new_score" -gt 100 ]; then new_score=100; fi
|
|
58
|
+
if [ "$new_score" -lt -100 ]; then new_score=-100; fi
|
|
59
|
+
|
|
60
|
+
jq --arg cat "$category" \
|
|
61
|
+
--argjson score "$new_score" \
|
|
62
|
+
--arg ts "$timestamp" \
|
|
63
|
+
'
|
|
64
|
+
.lastUpdated = $ts |
|
|
65
|
+
.preferences.categories[$cat] = $score
|
|
66
|
+
' "$PROFILE_FILE" > "$PROFILE_FILE.tmp" && mv "$PROFILE_FILE.tmp" "$PROFILE_FILE"
|
|
67
|
+
|
|
68
|
+
echo "Updated category preference: $category = $new_score"
|
|
69
|
+
fi
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# 프로젝트 타입 선호도 업데이트
|
|
73
|
+
update_project_type_preference() {
|
|
74
|
+
local project_type="$1"
|
|
75
|
+
local plugin_name="$2"
|
|
76
|
+
local success="$3" # true/false
|
|
77
|
+
|
|
78
|
+
init_profile
|
|
79
|
+
|
|
80
|
+
if command -v jq &> /dev/null; then
|
|
81
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
82
|
+
local weight=1
|
|
83
|
+
if [ "$success" = "true" ]; then weight=1; else weight=-1; fi
|
|
84
|
+
|
|
85
|
+
local current=$(jq -r ".preferences.projectTypes[\"$project_type\"].score // 0" "$PROFILE_FILE")
|
|
86
|
+
local new_score=$((current + weight))
|
|
87
|
+
|
|
88
|
+
jq --arg pt "$project_type" \
|
|
89
|
+
--arg plugin "$plugin_name" \
|
|
90
|
+
--argjson score "$new_score" \
|
|
91
|
+
--arg ts "$timestamp" \
|
|
92
|
+
'
|
|
93
|
+
.lastUpdated = $ts |
|
|
94
|
+
.preferences.projectTypes[$pt].score = $score |
|
|
95
|
+
.preferences.projectTypes[$pt].plugins = ((.preferences.projectTypes[$pt].plugins // []) + [$plugin] | unique)
|
|
96
|
+
' "$PROFILE_FILE" > "$PROFILE_FILE.tmp" && mv "$PROFILE_FILE.tmp" "$PROFILE_FILE"
|
|
97
|
+
|
|
98
|
+
echo "Updated project type preference: $project_type"
|
|
99
|
+
fi
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# 활동 패턴 기록
|
|
103
|
+
record_activity() {
|
|
104
|
+
local action="$1"
|
|
105
|
+
local hour=$(date +"%H")
|
|
106
|
+
local day=$(date +"%u") # 1-7 (월-일)
|
|
107
|
+
local week=$(date +"%W")
|
|
108
|
+
|
|
109
|
+
init_profile
|
|
110
|
+
|
|
111
|
+
if command -v jq &> /dev/null; then
|
|
112
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
113
|
+
|
|
114
|
+
jq --arg hour "$hour" \
|
|
115
|
+
--arg day "$day" \
|
|
116
|
+
--arg week "$week" \
|
|
117
|
+
--arg action "$action" \
|
|
118
|
+
--arg ts "$timestamp" \
|
|
119
|
+
'
|
|
120
|
+
.lastUpdated = $ts |
|
|
121
|
+
.activityPatterns.hourly[$hour] = ((.activityPatterns.hourly[$hour] // 0) + 1) |
|
|
122
|
+
.activityPatterns.daily[$day] = ((.activityPatterns.daily[$day] // 0) + 1) |
|
|
123
|
+
.activityPatterns.weekly[$week] = ((.activityPatterns.weekly[$week] // 0) + 1)
|
|
124
|
+
' "$PROFILE_FILE" > "$PROFILE_FILE.tmp" && mv "$PROFILE_FILE.tmp" "$PROFILE_FILE"
|
|
125
|
+
fi
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# 설치 이력 기록
|
|
129
|
+
record_install() {
|
|
130
|
+
local plugin_name="$1"
|
|
131
|
+
local category="$2"
|
|
132
|
+
local project_type="$3"
|
|
133
|
+
|
|
134
|
+
init_profile
|
|
135
|
+
|
|
136
|
+
if command -v jq &> /dev/null; then
|
|
137
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
138
|
+
|
|
139
|
+
jq --arg plugin "$plugin_name" \
|
|
140
|
+
--arg cat "$category" \
|
|
141
|
+
--arg pt "$project_type" \
|
|
142
|
+
--arg ts "$timestamp" \
|
|
143
|
+
'
|
|
144
|
+
.lastUpdated = $ts |
|
|
145
|
+
.installHistory = ([{
|
|
146
|
+
"plugin": $plugin,
|
|
147
|
+
"category": $cat,
|
|
148
|
+
"projectType": $pt,
|
|
149
|
+
"installedAt": $ts
|
|
150
|
+
}] + .installHistory) | .[0:100] |
|
|
151
|
+
.usageStats.totalInstalls = (.usageStats.totalInstalls + 1)
|
|
152
|
+
' "$PROFILE_FILE" > "$PROFILE_FILE.tmp" && mv "$PROFILE_FILE.tmp" "$PROFILE_FILE"
|
|
153
|
+
|
|
154
|
+
# 카테고리/프로젝트 타입 선호도도 업데이트
|
|
155
|
+
update_category_preference "$category" "install"
|
|
156
|
+
update_project_type_preference "$project_type" "$plugin_name" "true"
|
|
157
|
+
record_activity "install"
|
|
158
|
+
|
|
159
|
+
echo "Recorded install: $plugin_name"
|
|
160
|
+
fi
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# 추천 수락률 업데이트
|
|
164
|
+
update_accept_rate() {
|
|
165
|
+
local accepted="$1" # true/false
|
|
166
|
+
|
|
167
|
+
init_profile
|
|
168
|
+
|
|
169
|
+
if command -v jq &> /dev/null; then
|
|
170
|
+
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
171
|
+
local total=$(jq -r '.usageStats.totalRecommendations // 0' "$PROFILE_FILE")
|
|
172
|
+
local current_rate=$(jq -r '.usageStats.acceptRate // 0' "$PROFILE_FILE")
|
|
173
|
+
local new_total=$((total + 1))
|
|
174
|
+
|
|
175
|
+
local add_to_rate=0
|
|
176
|
+
if [ "$accepted" = "true" ]; then add_to_rate=1; fi
|
|
177
|
+
|
|
178
|
+
# 이동 평균 계산
|
|
179
|
+
local new_rate=$(echo "scale=2; ($current_rate * $total + $add_to_rate) / $new_total" | bc 2>/dev/null || echo "$current_rate")
|
|
180
|
+
|
|
181
|
+
jq --arg ts "$timestamp" \
|
|
182
|
+
--argjson total "$new_total" \
|
|
183
|
+
--arg rate "$new_rate" \
|
|
184
|
+
'
|
|
185
|
+
.lastUpdated = $ts |
|
|
186
|
+
.usageStats.totalRecommendations = $total |
|
|
187
|
+
.usageStats.acceptRate = ($rate | tonumber)
|
|
188
|
+
' "$PROFILE_FILE" > "$PROFILE_FILE.tmp" && mv "$PROFILE_FILE.tmp" "$PROFILE_FILE"
|
|
189
|
+
fi
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# 선호 카테고리 목록 반환
|
|
193
|
+
get_preferred_categories() {
|
|
194
|
+
local min_score="${1:-10}"
|
|
195
|
+
|
|
196
|
+
init_profile
|
|
197
|
+
|
|
198
|
+
if command -v jq &> /dev/null; then
|
|
199
|
+
jq -r --argjson min "$min_score" '
|
|
200
|
+
.preferences.categories | to_entries |
|
|
201
|
+
map(select(.value >= $min)) |
|
|
202
|
+
sort_by(-.value) |
|
|
203
|
+
.[].key
|
|
204
|
+
' "$PROFILE_FILE"
|
|
205
|
+
fi
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
# 가장 활동적인 시간대 반환
|
|
209
|
+
get_peak_hours() {
|
|
210
|
+
init_profile
|
|
211
|
+
|
|
212
|
+
if command -v jq &> /dev/null; then
|
|
213
|
+
jq -r '
|
|
214
|
+
.activityPatterns.hourly | to_entries |
|
|
215
|
+
sort_by(-.value) |
|
|
216
|
+
.[0:3] |
|
|
217
|
+
.[].key
|
|
218
|
+
' "$PROFILE_FILE"
|
|
219
|
+
fi
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
# 프로필 요약 출력
|
|
223
|
+
get_profile_summary() {
|
|
224
|
+
init_profile
|
|
225
|
+
|
|
226
|
+
if [ ! -f "$PROFILE_FILE" ]; then
|
|
227
|
+
echo "No profile data"
|
|
228
|
+
return
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
if ! command -v jq &> /dev/null; then
|
|
232
|
+
echo "jq required for profile summary"
|
|
233
|
+
return
|
|
234
|
+
fi
|
|
235
|
+
|
|
236
|
+
echo "=== Profile Summary ==="
|
|
237
|
+
echo ""
|
|
238
|
+
echo "Preferred Categories (top 5):"
|
|
239
|
+
jq -r '.preferences.categories | to_entries | sort_by(-.value) | .[0:5] | .[] | " \(.key): \(.value)"' "$PROFILE_FILE"
|
|
240
|
+
echo ""
|
|
241
|
+
echo "Project Types:"
|
|
242
|
+
jq -r '.preferences.projectTypes | to_entries | .[0:5] | .[] | " \(.key): \(.value.score // 0) (\(.value.plugins | length) plugins)"' "$PROFILE_FILE"
|
|
243
|
+
echo ""
|
|
244
|
+
echo "Activity Patterns:"
|
|
245
|
+
echo " Peak hours: $(get_peak_hours | tr '\n' ', ' | sed 's/,$//')"
|
|
246
|
+
echo ""
|
|
247
|
+
echo "Stats:"
|
|
248
|
+
jq -r '" Total installs: \(.usageStats.totalInstalls)\n Accept rate: \(.usageStats.acceptRate | . * 100 | floor)%"' "$PROFILE_FILE"
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
# 프로필 기반 추천 점수 조정
|
|
252
|
+
get_score_adjustment() {
|
|
253
|
+
local plugin_name="$1"
|
|
254
|
+
local category="$2"
|
|
255
|
+
local project_type="$3"
|
|
256
|
+
|
|
257
|
+
init_profile
|
|
258
|
+
|
|
259
|
+
if ! command -v jq &> /dev/null; then
|
|
260
|
+
echo "0"
|
|
261
|
+
return
|
|
262
|
+
fi
|
|
263
|
+
|
|
264
|
+
local cat_score=$(jq -r ".preferences.categories[\"$category\"] // 0" "$PROFILE_FILE")
|
|
265
|
+
local pt_score=$(jq -r ".preferences.projectTypes[\"$project_type\"].score // 0" "$PROFILE_FILE")
|
|
266
|
+
|
|
267
|
+
# 선호도에 따라 -20 ~ +20 범위로 점수 조정
|
|
268
|
+
local adjustment=$(echo "scale=0; ($cat_score + $pt_score) / 10" | bc 2>/dev/null || echo "0")
|
|
269
|
+
|
|
270
|
+
if [ "$adjustment" -gt 20 ]; then adjustment=20; fi
|
|
271
|
+
if [ "$adjustment" -lt -20 ]; then adjustment=-20; fi
|
|
272
|
+
|
|
273
|
+
echo "$adjustment"
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
# 프로필 초기화 (리셋)
|
|
277
|
+
reset_profile() {
|
|
278
|
+
if [ -f "$PROFILE_FILE" ]; then
|
|
279
|
+
rm "$PROFILE_FILE"
|
|
280
|
+
init_profile
|
|
281
|
+
echo "Profile reset successfully"
|
|
282
|
+
else
|
|
283
|
+
echo "No profile to reset"
|
|
284
|
+
fi
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
# CLI 인터페이스
|
|
288
|
+
case "$1" in
|
|
289
|
+
init)
|
|
290
|
+
init_profile
|
|
291
|
+
echo "Profile initialized"
|
|
292
|
+
;;
|
|
293
|
+
category)
|
|
294
|
+
update_category_preference "$2" "$3"
|
|
295
|
+
;;
|
|
296
|
+
project-type)
|
|
297
|
+
update_project_type_preference "$2" "$3" "$4"
|
|
298
|
+
;;
|
|
299
|
+
activity)
|
|
300
|
+
record_activity "$2"
|
|
301
|
+
;;
|
|
302
|
+
install)
|
|
303
|
+
record_install "$2" "$3" "$4"
|
|
304
|
+
;;
|
|
305
|
+
accept-rate)
|
|
306
|
+
update_accept_rate "$2"
|
|
307
|
+
;;
|
|
308
|
+
preferred-categories)
|
|
309
|
+
get_preferred_categories "$2"
|
|
310
|
+
;;
|
|
311
|
+
peak-hours)
|
|
312
|
+
get_peak_hours
|
|
313
|
+
;;
|
|
314
|
+
summary)
|
|
315
|
+
get_profile_summary
|
|
316
|
+
;;
|
|
317
|
+
score-adjustment)
|
|
318
|
+
get_score_adjustment "$2" "$3" "$4"
|
|
319
|
+
;;
|
|
320
|
+
reset)
|
|
321
|
+
reset_profile
|
|
322
|
+
;;
|
|
323
|
+
*)
|
|
324
|
+
echo "Usage: $0 {init|category|project-type|activity|install|accept-rate|preferred-categories|peak-hours|summary|score-adjustment|reset}"
|
|
325
|
+
echo ""
|
|
326
|
+
echo "Commands:"
|
|
327
|
+
echo " init - Initialize profile"
|
|
328
|
+
echo " category <cat> <action> - Update category preference (accept|reject|install|uninstall)"
|
|
329
|
+
echo " project-type <type> <plugin> <success> - Update project type preference"
|
|
330
|
+
echo " activity <action> - Record activity pattern"
|
|
331
|
+
echo " install <plugin> <cat> <project_type> - Record plugin install"
|
|
332
|
+
echo " accept-rate <true|false> - Update recommendation accept rate"
|
|
333
|
+
echo " preferred-categories [min_score] - Get preferred categories"
|
|
334
|
+
echo " peak-hours - Get peak activity hours"
|
|
335
|
+
echo " summary - Show profile summary"
|
|
336
|
+
echo " score-adjustment <plugin> <cat> <pt> - Get score adjustment based on profile"
|
|
337
|
+
echo " reset - Reset profile"
|
|
338
|
+
;;
|
|
339
|
+
esac
|