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,221 @@
1
+ #!/bin/bash
2
+ # monol-server 동기화 모듈 (scout)
3
+ # 플러그인 이벤트를 서버로 전송
4
+
5
+ # 설정 로드
6
+ load_sync_config() {
7
+ local config_file="${PLUGIN_ROOT:-$(dirname "$0")/..}/config.yaml"
8
+
9
+ if [ -f "$config_file" ]; then
10
+ SYNC_ENABLED=$(grep "^sync_enabled:" "$config_file" 2>/dev/null | awk '{print $2}' | tr -d '"')
11
+ SYNC_SERVER_URL=$(grep "^sync_server_url:" "$config_file" 2>/dev/null | awk '{print $2}' | tr -d '"')
12
+ SYNC_TEAM=$(grep "^sync_team:" "$config_file" 2>/dev/null | awk '{print $2}' | tr -d '"')
13
+ SYNC_TIMEOUT=$(grep "^sync_timeout:" "$config_file" 2>/dev/null | awk '{print $2}' | tr -d '"')
14
+ fi
15
+
16
+ # 환경변수 우선
17
+ SYNC_ENABLED="${MONOL_SYNC_ENABLED:-${SYNC_ENABLED:-true}}"
18
+ SYNC_SERVER_URL="${MONOL_SERVER_URL:-${SYNC_SERVER_URL:-http://localhost:3030}}"
19
+ SYNC_TEAM="${MONOL_TEAM:-${SYNC_TEAM:-}}"
20
+ SYNC_TIMEOUT="${SYNC_TIMEOUT:-5}"
21
+
22
+ # 사용자 감지
23
+ SYNC_USER="${MONOL_USER:-$(git config user.name 2>/dev/null || echo "$USER")}"
24
+ }
25
+
26
+ # 서버 연결 확인
27
+ check_server() {
28
+ local health_url="${SYNC_SERVER_URL}/api/health"
29
+
30
+ if curl -s --max-time 2 "$health_url" > /dev/null 2>&1; then
31
+ return 0
32
+ else
33
+ return 1
34
+ fi
35
+ }
36
+
37
+ # 이벤트 전송
38
+ send_event() {
39
+ local event_type="$1"
40
+ local event_data="$2"
41
+
42
+ # 동기화 비활성화 확인
43
+ if [ "$SYNC_ENABLED" != "true" ]; then
44
+ return 0
45
+ fi
46
+
47
+ # 서버 URL 확인
48
+ if [ -z "$SYNC_SERVER_URL" ]; then
49
+ return 0
50
+ fi
51
+
52
+ # JSON 페이로드 생성
53
+ local payload=$(cat <<EOF
54
+ {
55
+ "user": "$SYNC_USER",
56
+ "team": "$SYNC_TEAM",
57
+ "plugin": "monol-scout",
58
+ "type": "$event_type",
59
+ "data": $event_data
60
+ }
61
+ EOF
62
+ )
63
+
64
+ # 전송 (실패해도 무시)
65
+ local response
66
+ response=$(curl -s --max-time "$SYNC_TIMEOUT" \
67
+ -X POST \
68
+ -H "Content-Type: application/json" \
69
+ -d "$payload" \
70
+ "${SYNC_SERVER_URL}/api/events" 2>/dev/null)
71
+
72
+ if echo "$response" | grep -q '"success":true' 2>/dev/null; then
73
+ return 0
74
+ else
75
+ return 1
76
+ fi
77
+ }
78
+
79
+ # 플러그인 검색 이벤트
80
+ sync_plugin_searched() {
81
+ local query="$1"
82
+ local category="$2"
83
+ local results_count="$3"
84
+
85
+ load_sync_config
86
+
87
+ local event_data=$(cat <<EOF
88
+ {
89
+ "query": "$query",
90
+ "category": "$category",
91
+ "resultsCount": ${results_count:-0}
92
+ }
93
+ EOF
94
+ )
95
+
96
+ send_event "plugin_searched" "$event_data"
97
+ }
98
+
99
+ # 플러그인 설치 이벤트
100
+ sync_plugin_installed() {
101
+ local plugin_name="$1"
102
+ local plugin_version="$2"
103
+ local source="$3"
104
+
105
+ load_sync_config
106
+
107
+ local event_data=$(cat <<EOF
108
+ {
109
+ "pluginName": "$plugin_name",
110
+ "version": "$plugin_version",
111
+ "source": "$source"
112
+ }
113
+ EOF
114
+ )
115
+
116
+ send_event "plugin_installed" "$event_data"
117
+ }
118
+
119
+ # 플러그인 제거 이벤트
120
+ sync_plugin_removed() {
121
+ local plugin_name="$1"
122
+
123
+ load_sync_config
124
+
125
+ local event_data=$(cat <<EOF
126
+ {
127
+ "pluginName": "$plugin_name"
128
+ }
129
+ EOF
130
+ )
131
+
132
+ send_event "plugin_removed" "$event_data"
133
+ }
134
+
135
+ # 스킬 사용 이벤트
136
+ sync_skill_used() {
137
+ local skill_name="$1"
138
+
139
+ load_sync_config
140
+
141
+ local event_data=$(cat <<EOF
142
+ {
143
+ "skillName": "$skill_name"
144
+ }
145
+ EOF
146
+ )
147
+
148
+ send_event "skill_used" "$event_data"
149
+ }
150
+
151
+ # 감사 실행 이벤트
152
+ sync_audit_run() {
153
+ local plugins_count="$1"
154
+ local issues_found="$2"
155
+
156
+ load_sync_config
157
+
158
+ local event_data=$(cat <<EOF
159
+ {
160
+ "pluginsCount": ${plugins_count:-0},
161
+ "issuesFound": ${issues_found:-0}
162
+ }
163
+ EOF
164
+ )
165
+
166
+ send_event "audit_run" "$event_data"
167
+ }
168
+
169
+ # 신뢰된 자동 설치 이벤트
170
+ sync_trusted_install() {
171
+ local installed_count="$1"
172
+ local skipped_count="$2"
173
+ local new_releases="$3"
174
+
175
+ load_sync_config
176
+
177
+ local event_data=$(cat <<EOF
178
+ {
179
+ "installedCount": ${installed_count:-0},
180
+ "skippedCount": ${skipped_count:-0},
181
+ "newReleases": ${new_releases:-0}
182
+ }
183
+ EOF
184
+ )
185
+
186
+ send_event "trusted_install" "$event_data"
187
+ }
188
+
189
+ # 스킬 설치 이벤트
190
+ sync_skill_installed() {
191
+ local skill_id="$1"
192
+ local source="$2"
193
+
194
+ load_sync_config
195
+
196
+ local event_data=$(cat <<EOF
197
+ {
198
+ "skillId": "$skill_id",
199
+ "source": "$source"
200
+ }
201
+ EOF
202
+ )
203
+
204
+ send_event "skill_installed" "$event_data"
205
+ }
206
+
207
+ # 정리 실행 이벤트
208
+ sync_cleanup_run() {
209
+ local removed_count="$1"
210
+
211
+ load_sync_config
212
+
213
+ local event_data=$(cat <<EOF
214
+ {
215
+ "removedCount": ${removed_count:-0}
216
+ }
217
+ EOF
218
+ )
219
+
220
+ send_event "cleanup_run" "$event_data"
221
+ }
@@ -0,0 +1,450 @@
1
+ #!/bin/bash
2
+ # Plugin Scout - 협업 학습 시스템
3
+ # 팀원 선택 패턴 통합, 팀 필수/금지 플러그인 정책, 온보딩 추천
4
+
5
+ PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}"
6
+ DATA_DIR="$PLUGIN_ROOT/data"
7
+ TEAM_FILE="$DATA_DIR/team.json"
8
+ TEAM_CONFIG_FILE=".claude/team-plugins.json"
9
+
10
+ # 팀 데이터 초기화
11
+ init_team() {
12
+ if [ ! -f "$TEAM_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
+ "members": {},
18
+ "sharedSelections": {},
19
+ "policies": {
20
+ "required": [],
21
+ "forbidden": [],
22
+ "recommended": []
23
+ },
24
+ "onboarding": {
25
+ "completedMembers": [],
26
+ "pendingMembers": []
27
+ },
28
+ "stats": {
29
+ "totalMembers": 0,
30
+ "activeMembers": 0,
31
+ "commonPlugins": []
32
+ }
33
+ }' > "$TEAM_FILE"
34
+ fi
35
+ }
36
+
37
+ # 팀 설정 파일 로드
38
+ load_team_config() {
39
+ local project_path="${1:-.}"
40
+ local config_file="$project_path/$TEAM_CONFIG_FILE"
41
+
42
+ if [ -f "$config_file" ] && command -v jq &> /dev/null; then
43
+ cat "$config_file"
44
+ else
45
+ echo '{}'
46
+ fi
47
+ }
48
+
49
+ # 팀 멤버 등록/업데이트
50
+ register_member() {
51
+ local member_id="$1"
52
+ local member_name="$2"
53
+
54
+ init_team
55
+
56
+ if ! command -v jq &> /dev/null; then
57
+ echo "jq required"
58
+ return
59
+ fi
60
+
61
+ local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
62
+ local exists=$(jq -r --arg id "$member_id" '.members[$id] // empty' "$TEAM_FILE")
63
+
64
+ if [ -z "$exists" ]; then
65
+ # 새 멤버 - 온보딩 대기열에 추가
66
+ jq --arg id "$member_id" \
67
+ --arg name "$member_name" \
68
+ --arg ts "$timestamp" \
69
+ '
70
+ .lastUpdated = $ts |
71
+ .members[$id] = {
72
+ "name": $name,
73
+ "joinedAt": $ts,
74
+ "lastActive": $ts,
75
+ "selections": [],
76
+ "onboardingCompleted": false
77
+ } |
78
+ .onboarding.pendingMembers = (.onboarding.pendingMembers + [$id] | unique) |
79
+ .stats.totalMembers = (.members | length)
80
+ ' "$TEAM_FILE" > "$TEAM_FILE.tmp" && mv "$TEAM_FILE.tmp" "$TEAM_FILE"
81
+
82
+ echo "Registered new member: $member_name ($member_id)"
83
+ else
84
+ # 기존 멤버 - 활동 시간 업데이트
85
+ jq --arg id "$member_id" --arg ts "$timestamp" '
86
+ .lastUpdated = $ts |
87
+ .members[$id].lastActive = $ts
88
+ ' "$TEAM_FILE" > "$TEAM_FILE.tmp" && mv "$TEAM_FILE.tmp" "$TEAM_FILE"
89
+
90
+ echo "Updated member activity: $member_id"
91
+ fi
92
+ }
93
+
94
+ # 멤버의 플러그인 선택 기록
95
+ record_member_selection() {
96
+ local member_id="$1"
97
+ local plugin_name="$2"
98
+ local action="$3" # install, uninstall, accept, reject
99
+
100
+ init_team
101
+
102
+ if ! command -v jq &> /dev/null; then
103
+ echo "jq required"
104
+ return
105
+ fi
106
+
107
+ local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
108
+
109
+ # 멤버 선택 기록
110
+ jq --arg id "$member_id" \
111
+ --arg plugin "$plugin_name" \
112
+ --arg action "$action" \
113
+ --arg ts "$timestamp" \
114
+ '
115
+ .lastUpdated = $ts |
116
+ .members[$id].selections = ((.members[$id].selections // []) + [{
117
+ "plugin": $plugin,
118
+ "action": $action,
119
+ "at": $ts
120
+ }]) | .[-20:] |
121
+ .members[$id].lastActive = $ts
122
+ ' "$TEAM_FILE" > "$TEAM_FILE.tmp" && mv "$TEAM_FILE.tmp" "$TEAM_FILE"
123
+
124
+ # 공유 선택 업데이트
125
+ if [ "$action" = "install" ] || [ "$action" = "accept" ]; then
126
+ jq --arg plugin "$plugin_name" --arg ts "$timestamp" '
127
+ .sharedSelections[$plugin] = {
128
+ "count": ((.sharedSelections[$plugin].count // 0) + 1),
129
+ "lastSelected": $ts
130
+ }
131
+ ' "$TEAM_FILE" > "$TEAM_FILE.tmp" && mv "$TEAM_FILE.tmp" "$TEAM_FILE"
132
+ fi
133
+
134
+ echo "Recorded selection: $member_id -> $plugin_name ($action)"
135
+ }
136
+
137
+ # 팀 정책 설정
138
+ set_policy() {
139
+ local policy_type="$1" # required, forbidden, recommended
140
+ local plugin_name="$2"
141
+ local action="$3" # add, remove
142
+
143
+ init_team
144
+
145
+ if ! command -v jq &> /dev/null; then
146
+ echo "jq required"
147
+ return
148
+ fi
149
+
150
+ local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
151
+
152
+ if [ "$action" = "add" ]; then
153
+ jq --arg type "$policy_type" \
154
+ --arg plugin "$plugin_name" \
155
+ --arg ts "$timestamp" \
156
+ '
157
+ .lastUpdated = $ts |
158
+ .policies[$type] = (.policies[$type] + [$plugin] | unique)
159
+ ' "$TEAM_FILE" > "$TEAM_FILE.tmp" && mv "$TEAM_FILE.tmp" "$TEAM_FILE"
160
+
161
+ echo "Added to $policy_type: $plugin_name"
162
+ else
163
+ jq --arg type "$policy_type" \
164
+ --arg plugin "$plugin_name" \
165
+ --arg ts "$timestamp" \
166
+ '
167
+ .lastUpdated = $ts |
168
+ .policies[$type] = (.policies[$type] | map(select(. != $plugin)))
169
+ ' "$TEAM_FILE" > "$TEAM_FILE.tmp" && mv "$TEAM_FILE.tmp" "$TEAM_FILE"
170
+
171
+ echo "Removed from $policy_type: $plugin_name"
172
+ fi
173
+ }
174
+
175
+ # 플러그인이 팀 정책에 맞는지 확인
176
+ check_policy() {
177
+ local plugin_name="$1"
178
+
179
+ init_team
180
+
181
+ if ! command -v jq &> /dev/null; then
182
+ echo "allowed"
183
+ return
184
+ fi
185
+
186
+ # 금지 목록 체크
187
+ local forbidden=$(jq -r --arg plugin "$plugin_name" '
188
+ if .policies.forbidden | index($plugin) then "forbidden" else "" end
189
+ ' "$TEAM_FILE")
190
+
191
+ if [ "$forbidden" = "forbidden" ]; then
192
+ echo "forbidden"
193
+ return
194
+ fi
195
+
196
+ # 필수 목록 체크 (추천용)
197
+ local required=$(jq -r --arg plugin "$plugin_name" '
198
+ if .policies.required | index($plugin) then "required" else "" end
199
+ ' "$TEAM_FILE")
200
+
201
+ if [ "$required" = "required" ]; then
202
+ echo "required"
203
+ return
204
+ fi
205
+
206
+ echo "allowed"
207
+ }
208
+
209
+ # 온보딩 상태 확인
210
+ check_onboarding() {
211
+ local member_id="$1"
212
+
213
+ init_team
214
+
215
+ if ! command -v jq &> /dev/null; then
216
+ echo "unknown"
217
+ return
218
+ fi
219
+
220
+ local completed=$(jq -r --arg id "$member_id" '
221
+ .members[$id].onboardingCompleted // false
222
+ ' "$TEAM_FILE")
223
+
224
+ echo "$completed"
225
+ }
226
+
227
+ # 온보딩 완료 처리
228
+ complete_onboarding() {
229
+ local member_id="$1"
230
+
231
+ init_team
232
+
233
+ if ! command -v jq &> /dev/null; then
234
+ echo "jq required"
235
+ return
236
+ fi
237
+
238
+ local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
239
+
240
+ jq --arg id "$member_id" --arg ts "$timestamp" '
241
+ .lastUpdated = $ts |
242
+ .members[$id].onboardingCompleted = true |
243
+ .onboarding.completedMembers = (.onboarding.completedMembers + [$id] | unique) |
244
+ .onboarding.pendingMembers = (.onboarding.pendingMembers | map(select(. != $id)))
245
+ ' "$TEAM_FILE" > "$TEAM_FILE.tmp" && mv "$TEAM_FILE.tmp" "$TEAM_FILE"
246
+
247
+ echo "Onboarding completed for: $member_id"
248
+ }
249
+
250
+ # 온보딩 추천 플러그인 목록
251
+ get_onboarding_recommendations() {
252
+ local member_id="$1"
253
+
254
+ init_team
255
+
256
+ if ! command -v jq &> /dev/null; then
257
+ echo "[]"
258
+ return
259
+ fi
260
+
261
+ # 필수 + 추천 + 팀에서 많이 사용하는 플러그인
262
+ jq '
263
+ (.policies.required + .policies.recommended) as $policy |
264
+ (.sharedSelections | to_entries | sort_by(-.value.count) | .[0:5] | map(.key)) as $popular |
265
+ ($policy + $popular) | unique
266
+ ' "$TEAM_FILE"
267
+ }
268
+
269
+ # 팀원 통계 업데이트
270
+ update_team_stats() {
271
+ init_team
272
+
273
+ if ! command -v jq &> /dev/null; then
274
+ echo "jq required"
275
+ return
276
+ fi
277
+
278
+ local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
279
+
280
+ # 7일 이내 활동한 멤버를 활성 멤버로 간주
281
+ local cutoff=$(date -u -v-7d +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -d "7 days ago" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null)
282
+
283
+ jq --arg cutoff "$cutoff" --arg ts "$timestamp" '
284
+ .lastUpdated = $ts |
285
+ .stats.totalMembers = (.members | length) |
286
+ .stats.activeMembers = ([.members | to_entries[] | select(.value.lastActive >= $cutoff)] | length) |
287
+ .stats.commonPlugins = (
288
+ .sharedSelections | to_entries |
289
+ sort_by(-.value.count) |
290
+ .[0:10] |
291
+ map(.key)
292
+ )
293
+ ' "$TEAM_FILE" > "$TEAM_FILE.tmp" && mv "$TEAM_FILE.tmp" "$TEAM_FILE"
294
+
295
+ echo "Team stats updated"
296
+ }
297
+
298
+ # 팀 공통 플러그인 추천
299
+ get_team_recommendations() {
300
+ local member_id="$1"
301
+
302
+ init_team
303
+
304
+ if ! command -v jq &> /dev/null; then
305
+ echo "[]"
306
+ return
307
+ fi
308
+
309
+ # 멤버가 아직 사용하지 않는 팀 공통 플러그인 추천
310
+ jq --arg id "$member_id" '
311
+ (.members[$id].selections | map(.plugin) | unique) as $memberPlugins |
312
+ .sharedSelections | to_entries |
313
+ map(select(.key as $k | $memberPlugins | index($k) | not)) |
314
+ sort_by(-.value.count) |
315
+ .[0:5] |
316
+ map(.key)
317
+ ' "$TEAM_FILE"
318
+ }
319
+
320
+ # 팀 요약 출력
321
+ get_team_summary() {
322
+ init_team
323
+ update_team_stats
324
+
325
+ if ! command -v jq &> /dev/null; then
326
+ echo "jq required"
327
+ return
328
+ fi
329
+
330
+ echo "=== Team Summary ==="
331
+ echo ""
332
+ echo "Members:"
333
+ echo " Total: $(jq -r '.stats.totalMembers' "$TEAM_FILE")"
334
+ echo " Active (7d): $(jq -r '.stats.activeMembers' "$TEAM_FILE")"
335
+ echo ""
336
+ echo "Policies:"
337
+ echo " Required: $(jq -r '.policies.required | join(", ")' "$TEAM_FILE")"
338
+ echo " Forbidden: $(jq -r '.policies.forbidden | join(", ")' "$TEAM_FILE")"
339
+ echo " Recommended: $(jq -r '.policies.recommended | join(", ")' "$TEAM_FILE")"
340
+ echo ""
341
+ echo "Common Plugins:"
342
+ jq -r '.stats.commonPlugins | .[] | " - \(.)"' "$TEAM_FILE"
343
+ echo ""
344
+ echo "Onboarding:"
345
+ echo " Completed: $(jq -r '.onboarding.completedMembers | length' "$TEAM_FILE")"
346
+ echo " Pending: $(jq -r '.onboarding.pendingMembers | length' "$TEAM_FILE")"
347
+ }
348
+
349
+ # 멤버 사용 리포트
350
+ get_member_report() {
351
+ local member_id="$1"
352
+
353
+ init_team
354
+
355
+ if ! command -v jq &> /dev/null; then
356
+ echo "jq required"
357
+ return
358
+ fi
359
+
360
+ echo "=== Member Report: $member_id ==="
361
+ echo ""
362
+ jq -r --arg id "$member_id" '
363
+ .members[$id] | if . then
364
+ "Name: \(.name)\nJoined: \(.joinedAt | split("T")[0])\nLast Active: \(.lastActive | split("T")[0])\nOnboarding: \(.onboardingCompleted)\n\nRecent Selections:"
365
+ else
366
+ "Member not found"
367
+ end
368
+ ' "$TEAM_FILE"
369
+
370
+ jq -r --arg id "$member_id" '
371
+ .members[$id].selections | if . then
372
+ reverse | .[0:10] | .[] | " \(.at | split("T")[0]): \(.plugin) (\(.action))"
373
+ else
374
+ ""
375
+ end
376
+ ' "$TEAM_FILE"
377
+ }
378
+
379
+ # 팀 데이터 리셋
380
+ reset_team() {
381
+ if [ -f "$TEAM_FILE" ]; then
382
+ rm "$TEAM_FILE"
383
+ init_team
384
+ echo "Team data reset successfully"
385
+ else
386
+ echo "No team data to reset"
387
+ fi
388
+ }
389
+
390
+ # CLI 인터페이스
391
+ case "$1" in
392
+ init)
393
+ init_team
394
+ echo "Team learner initialized"
395
+ ;;
396
+ register)
397
+ register_member "$2" "$3"
398
+ ;;
399
+ selection)
400
+ record_member_selection "$2" "$3" "$4"
401
+ ;;
402
+ policy)
403
+ set_policy "$2" "$3" "$4"
404
+ ;;
405
+ check-policy)
406
+ check_policy "$2"
407
+ ;;
408
+ check-onboarding)
409
+ check_onboarding "$2"
410
+ ;;
411
+ complete-onboarding)
412
+ complete_onboarding "$2"
413
+ ;;
414
+ onboarding-recs)
415
+ get_onboarding_recommendations "$2"
416
+ ;;
417
+ team-recs)
418
+ get_team_recommendations "$2"
419
+ ;;
420
+ update-stats)
421
+ update_team_stats
422
+ ;;
423
+ summary)
424
+ get_team_summary
425
+ ;;
426
+ member-report)
427
+ get_member_report "$2"
428
+ ;;
429
+ reset)
430
+ reset_team
431
+ ;;
432
+ *)
433
+ echo "Usage: $0 {init|register|selection|policy|check-policy|check-onboarding|complete-onboarding|onboarding-recs|team-recs|update-stats|summary|member-report|reset}"
434
+ echo ""
435
+ echo "Commands:"
436
+ echo " init - Initialize team learner"
437
+ echo " register <member_id> <name> - Register/update team member"
438
+ echo " selection <member_id> <plugin> <action> - Record member plugin selection"
439
+ echo " policy <type> <plugin> <add|remove> - Set team policy (required|forbidden|recommended)"
440
+ echo " check-policy <plugin> - Check plugin against team policy"
441
+ echo " check-onboarding <member_id> - Check onboarding status"
442
+ echo " complete-onboarding <member_id> - Mark onboarding as completed"
443
+ echo " onboarding-recs <member_id> - Get onboarding recommendations"
444
+ echo " team-recs <member_id> - Get team-based recommendations"
445
+ echo " update-stats - Update team statistics"
446
+ echo " summary - Show team summary"
447
+ echo " member-report <member_id> - Show member report"
448
+ echo " reset - Reset team data"
449
+ ;;
450
+ esac