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.
Files changed (87) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +244 -0
  3. package/monol-plugin-scout-pkg/CLAUDE.md +125 -8
  4. package/monol-plugin-scout-pkg/api/mock/categories.json +81 -0
  5. package/monol-plugin-scout-pkg/api/mock/insights.json +111 -0
  6. package/monol-plugin-scout-pkg/api/mock/marketplace.json +501 -0
  7. package/monol-plugin-scout-pkg/api/mock/trending.json +96 -0
  8. package/monol-plugin-scout-pkg/commands/console.md +79 -0
  9. package/monol-plugin-scout-pkg/commands/frequency.md +32 -0
  10. package/monol-plugin-scout-pkg/commands/install.md +32 -0
  11. package/monol-plugin-scout-pkg/commands/priority.md +30 -0
  12. package/monol-plugin-scout-pkg/commands/quiet.md +32 -0
  13. package/monol-plugin-scout-pkg/commands/schedule.md +32 -0
  14. package/monol-plugin-scout-pkg/commands/scout.md +8 -5
  15. package/monol-plugin-scout-pkg/commands/skills.md +160 -0
  16. package/monol-plugin-scout-pkg/commands/team.md +32 -0
  17. package/monol-plugin-scout-pkg/commands/timing.md +32 -0
  18. package/monol-plugin-scout-pkg/commands/trust.md +85 -0
  19. package/monol-plugin-scout-pkg/commands/trusted-install.md +98 -0
  20. package/monol-plugin-scout-pkg/commands/uninstall.md +31 -0
  21. package/monol-plugin-scout-pkg/config.yaml +57 -0
  22. package/monol-plugin-scout-pkg/data/.cache/676d5ab664292155e5f509c69d4edae1 +10 -0
  23. package/monol-plugin-scout-pkg/data/.session +8 -0
  24. package/monol-plugin-scout-pkg/data/analytics.json +102 -0
  25. package/monol-plugin-scout-pkg/data/history.json +67 -11
  26. package/monol-plugin-scout-pkg/data/logs/scout.log +1 -0
  27. package/monol-plugin-scout-pkg/data/schedules.json +17 -0
  28. package/monol-plugin-scout-pkg/data/team.json +53 -0
  29. package/monol-plugin-scout-pkg/data/usage.json +100 -7
  30. package/monol-plugin-scout-pkg/docs/ARCHITECTURE.md +201 -0
  31. package/monol-plugin-scout-pkg/hooks/generate-insights.sh +102 -0
  32. package/monol-plugin-scout-pkg/hooks/hooks.json +36 -1
  33. package/monol-plugin-scout-pkg/hooks/on-session-end.sh +136 -0
  34. package/monol-plugin-scout-pkg/hooks/on-session-start.sh +43 -0
  35. package/monol-plugin-scout-pkg/hooks/on-stop.md +79 -0
  36. package/monol-plugin-scout-pkg/hooks/open-console.sh +111 -0
  37. package/monol-plugin-scout-pkg/hooks/open-dashboard.sh +61 -0
  38. package/monol-plugin-scout-pkg/hooks/track-usage.sh +59 -0
  39. package/monol-plugin-scout-pkg/lib/ai-recommender.sh +505 -0
  40. package/monol-plugin-scout-pkg/lib/cache.sh +194 -0
  41. package/monol-plugin-scout-pkg/lib/data-validator.sh +360 -0
  42. package/monol-plugin-scout-pkg/lib/error-handler.sh +296 -0
  43. package/monol-plugin-scout-pkg/lib/logger.sh +263 -0
  44. package/monol-plugin-scout-pkg/lib/plugin-manager.sh +239 -0
  45. package/monol-plugin-scout-pkg/lib/priority-scorer.sh +262 -0
  46. package/monol-plugin-scout-pkg/lib/profile-learner.sh +339 -0
  47. package/monol-plugin-scout-pkg/lib/project-analyzer.sh +281 -0
  48. package/monol-plugin-scout-pkg/lib/recommendation-controller.sh +290 -0
  49. package/monol-plugin-scout-pkg/lib/rejection-learner.sh +232 -0
  50. package/monol-plugin-scout-pkg/lib/scheduler.sh +275 -0
  51. package/monol-plugin-scout-pkg/lib/skill-scout.sh +729 -0
  52. package/monol-plugin-scout-pkg/lib/sync.sh +221 -0
  53. package/monol-plugin-scout-pkg/lib/team-learner.sh +450 -0
  54. package/monol-plugin-scout-pkg/lib/team-manager.sh +369 -0
  55. package/monol-plugin-scout-pkg/lib/trend-learner.sh +428 -0
  56. package/monol-plugin-scout-pkg/lib/trust-manager.sh +261 -0
  57. package/monol-plugin-scout-pkg/lib/trusted-installer.sh +738 -0
  58. package/monol-plugin-scout-pkg/plugin.json +3 -2
  59. package/monol-plugin-scout-pkg/skills/audit.md +6 -0
  60. package/monol-plugin-scout-pkg/skills/cleanup.md +6 -0
  61. package/monol-plugin-scout-pkg/skills/compare.md +6 -0
  62. package/monol-plugin-scout-pkg/skills/console.md +315 -0
  63. package/monol-plugin-scout-pkg/skills/explore.md +6 -0
  64. package/monol-plugin-scout-pkg/skills/fork.md +6 -0
  65. package/monol-plugin-scout-pkg/skills/frequency.md +93 -0
  66. package/monol-plugin-scout-pkg/skills/install.md +127 -0
  67. package/monol-plugin-scout-pkg/skills/priority.md +77 -0
  68. package/monol-plugin-scout-pkg/skills/quiet.md +73 -0
  69. package/monol-plugin-scout-pkg/skills/schedule.md +95 -0
  70. package/monol-plugin-scout-pkg/skills/scout.md +27 -17
  71. package/monol-plugin-scout-pkg/skills/skills.md +230 -0
  72. package/monol-plugin-scout-pkg/skills/team.md +117 -0
  73. package/monol-plugin-scout-pkg/skills/timing.md +97 -0
  74. package/monol-plugin-scout-pkg/skills/trust.md +120 -0
  75. package/monol-plugin-scout-pkg/skills/trusted-install.md +264 -0
  76. package/monol-plugin-scout-pkg/skills/uninstall.md +100 -0
  77. package/monol-plugin-scout-pkg/web/components/activity-chart.js +208 -0
  78. package/monol-plugin-scout-pkg/web/components/index.js +27 -0
  79. package/monol-plugin-scout-pkg/web/components/insight-card.js +365 -0
  80. package/monol-plugin-scout-pkg/web/components/overview.js +154 -0
  81. package/monol-plugin-scout-pkg/web/components/plugin-list.js +242 -0
  82. package/monol-plugin-scout-pkg/web/components/stats-card.js +126 -0
  83. package/monol-plugin-scout-pkg/web/components/team-list.js +346 -0
  84. package/monol-plugin-scout-pkg/web/console.html +2098 -0
  85. package/monol-plugin-scout-pkg/web/dashboard.html +2106 -0
  86. package/monol-plugin-scout-pkg/web/manifest.json +29 -0
  87. 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