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,281 @@
1
+ #!/bin/bash
2
+ # Plugin Scout - 프로젝트 분석기
3
+ # 프로젝트 구조와 의존성을 분석하여 추천에 활용
4
+
5
+ PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}"
6
+ PROJECT_ROOT="${PWD}"
7
+
8
+ # 프로젝트 타입 감지
9
+ detect_project_type() {
10
+ local types=()
11
+
12
+ # Node.js/JavaScript/TypeScript
13
+ if [ -f "$PROJECT_ROOT/package.json" ]; then
14
+ types+=("nodejs")
15
+ if [ -f "$PROJECT_ROOT/tsconfig.json" ]; then
16
+ types+=("typescript")
17
+ fi
18
+ fi
19
+
20
+ # Python
21
+ if [ -f "$PROJECT_ROOT/requirements.txt" ] || [ -f "$PROJECT_ROOT/pyproject.toml" ] || [ -f "$PROJECT_ROOT/setup.py" ]; then
22
+ types+=("python")
23
+ fi
24
+
25
+ # Rust
26
+ if [ -f "$PROJECT_ROOT/Cargo.toml" ]; then
27
+ types+=("rust")
28
+ fi
29
+
30
+ # Go
31
+ if [ -f "$PROJECT_ROOT/go.mod" ]; then
32
+ types+=("go")
33
+ fi
34
+
35
+ # Java
36
+ if [ -f "$PROJECT_ROOT/pom.xml" ] || [ -f "$PROJECT_ROOT/build.gradle" ]; then
37
+ types+=("java")
38
+ fi
39
+
40
+ # PHP
41
+ if [ -f "$PROJECT_ROOT/composer.json" ]; then
42
+ types+=("php")
43
+ fi
44
+
45
+ # Ruby
46
+ if [ -f "$PROJECT_ROOT/Gemfile" ]; then
47
+ types+=("ruby")
48
+ fi
49
+
50
+ # Claude Code Plugin
51
+ if [ -f "$PROJECT_ROOT/plugin.json" ] || [ -d "$PROJECT_ROOT/.claude" ]; then
52
+ types+=("claude-plugin")
53
+ fi
54
+
55
+ # Docker
56
+ if [ -f "$PROJECT_ROOT/Dockerfile" ] || [ -f "$PROJECT_ROOT/docker-compose.yml" ]; then
57
+ types+=("docker")
58
+ fi
59
+
60
+ # 결과 출력 (JSON)
61
+ printf '%s\n' "${types[@]}" | jq -R -s -c 'split("\n") | map(select(. != ""))'
62
+ }
63
+
64
+ # 프레임워크 감지
65
+ detect_frameworks() {
66
+ local frameworks=()
67
+
68
+ # Node.js 프레임워크 (package.json에서)
69
+ if [ -f "$PROJECT_ROOT/package.json" ]; then
70
+ local deps=$(cat "$PROJECT_ROOT/package.json" | grep -E '"(dependencies|devDependencies)"' -A 100 | head -100)
71
+
72
+ # Frontend
73
+ echo "$deps" | grep -q '"react"' && frameworks+=("react")
74
+ echo "$deps" | grep -q '"vue"' && frameworks+=("vue")
75
+ echo "$deps" | grep -q '"@angular/core"' && frameworks+=("angular")
76
+ echo "$deps" | grep -q '"svelte"' && frameworks+=("svelte")
77
+ echo "$deps" | grep -q '"next"' && frameworks+=("nextjs")
78
+ echo "$deps" | grep -q '"nuxt"' && frameworks+=("nuxtjs")
79
+ echo "$deps" | grep -q '"astro"' && frameworks+=("astro")
80
+
81
+ # Backend
82
+ echo "$deps" | grep -q '"express"' && frameworks+=("express")
83
+ echo "$deps" | grep -q '"fastify"' && frameworks+=("fastify")
84
+ echo "$deps" | grep -q '"@nestjs/core"' && frameworks+=("nestjs")
85
+ echo "$deps" | grep -q '"hono"' && frameworks+=("hono")
86
+ echo "$deps" | grep -q '"koa"' && frameworks+=("koa")
87
+
88
+ # Testing
89
+ echo "$deps" | grep -q '"jest"' && frameworks+=("jest")
90
+ echo "$deps" | grep -q '"vitest"' && frameworks+=("vitest")
91
+ echo "$deps" | grep -q '"mocha"' && frameworks+=("mocha")
92
+ echo "$deps" | grep -q '"playwright"' && frameworks+=("playwright")
93
+ echo "$deps" | grep -q '"cypress"' && frameworks+=("cypress")
94
+
95
+ # Build tools
96
+ echo "$deps" | grep -q '"webpack"' && frameworks+=("webpack")
97
+ echo "$deps" | grep -q '"vite"' && frameworks+=("vite")
98
+ echo "$deps" | grep -q '"esbuild"' && frameworks+=("esbuild")
99
+ echo "$deps" | grep -q '"turbo"' && frameworks+=("turborepo")
100
+
101
+ # Styling
102
+ echo "$deps" | grep -q '"tailwindcss"' && frameworks+=("tailwind")
103
+ echo "$deps" | grep -q '"styled-components"' && frameworks+=("styled-components")
104
+
105
+ # State management
106
+ echo "$deps" | grep -q '"redux"' && frameworks+=("redux")
107
+ echo "$deps" | grep -q '"zustand"' && frameworks+=("zustand")
108
+ echo "$deps" | grep -q '"jotai"' && frameworks+=("jotai")
109
+
110
+ # Database/ORM
111
+ echo "$deps" | grep -q '"prisma"' && frameworks+=("prisma")
112
+ echo "$deps" | grep -q '"drizzle-orm"' && frameworks+=("drizzle")
113
+ echo "$deps" | grep -q '"mongoose"' && frameworks+=("mongoose")
114
+ echo "$deps" | grep -q '"typeorm"' && frameworks+=("typeorm")
115
+
116
+ # API
117
+ echo "$deps" | grep -q '"graphql"' && frameworks+=("graphql")
118
+ echo "$deps" | grep -q '"trpc"' && frameworks+=("trpc")
119
+ fi
120
+
121
+ # Python 프레임워크
122
+ if [ -f "$PROJECT_ROOT/requirements.txt" ]; then
123
+ local reqs=$(cat "$PROJECT_ROOT/requirements.txt")
124
+ echo "$reqs" | grep -qi "django" && frameworks+=("django")
125
+ echo "$reqs" | grep -qi "flask" && frameworks+=("flask")
126
+ echo "$reqs" | grep -qi "fastapi" && frameworks+=("fastapi")
127
+ echo "$reqs" | grep -qi "pytest" && frameworks+=("pytest")
128
+ fi
129
+
130
+ printf '%s\n' "${frameworks[@]}" | jq -R -s -c 'split("\n") | map(select(. != ""))'
131
+ }
132
+
133
+ # 의존성 추출 (상위 20개)
134
+ get_dependencies() {
135
+ local deps=()
136
+
137
+ if [ -f "$PROJECT_ROOT/package.json" ] && command -v jq &> /dev/null; then
138
+ # dependencies
139
+ local prod_deps=$(jq -r '.dependencies // {} | keys[]' "$PROJECT_ROOT/package.json" 2>/dev/null | head -10)
140
+ # devDependencies
141
+ local dev_deps=$(jq -r '.devDependencies // {} | keys[]' "$PROJECT_ROOT/package.json" 2>/dev/null | head -10)
142
+
143
+ for dep in $prod_deps $dev_deps; do
144
+ deps+=("$dep")
145
+ done
146
+ fi
147
+
148
+ printf '%s\n' "${deps[@]}" | jq -R -s -c 'split("\n") | map(select(. != "")) | unique | .[:20]'
149
+ }
150
+
151
+ # 프로젝트 크기/복잡도 분석
152
+ analyze_complexity() {
153
+ local file_count=$(find "$PROJECT_ROOT" -type f -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" -o -name "*.py" -o -name "*.rs" -o -name "*.go" 2>/dev/null | wc -l | tr -d ' ')
154
+ local dir_count=$(find "$PROJECT_ROOT" -type d 2>/dev/null | wc -l | tr -d ' ')
155
+
156
+ local size="small"
157
+ if [ "$file_count" -gt 100 ]; then
158
+ size="large"
159
+ elif [ "$file_count" -gt 30 ]; then
160
+ size="medium"
161
+ fi
162
+
163
+ echo "{\"files\": $file_count, \"directories\": $dir_count, \"size\": \"$size\"}"
164
+ }
165
+
166
+ # 추천할 플러그인 카테고리 결정
167
+ suggest_categories() {
168
+ local categories=()
169
+ local types=$(detect_project_type)
170
+ local frameworks=$(detect_frameworks)
171
+
172
+ # TypeScript 프로젝트면 development 추천
173
+ echo "$types" | grep -q "typescript" && categories+=("development")
174
+
175
+ # 테스트 프레임워크 있으면 testing 추천
176
+ echo "$frameworks" | grep -qE "(jest|vitest|pytest|playwright|cypress)" && categories+=("testing")
177
+
178
+ # Frontend 프레임워크 있으면 frontend 추천
179
+ echo "$frameworks" | grep -qE "(react|vue|angular|svelte)" && categories+=("frontend")
180
+
181
+ # Docker 있으면 devops 추천
182
+ echo "$types" | grep -q "docker" && categories+=("devops")
183
+
184
+ # Database/ORM 사용하면 database 추천
185
+ echo "$frameworks" | grep -qE "(prisma|drizzle|mongoose|typeorm)" && categories+=("database")
186
+
187
+ # GraphQL 사용하면 api 추천
188
+ echo "$frameworks" | grep -qE "(graphql|trpc)" && categories+=("api")
189
+
190
+ # AI/ML 관련이면 ai 추천
191
+ echo "$frameworks" | grep -qE "(openai|langchain|huggingface)" && categories+=("ai")
192
+
193
+ # 기본적으로 productivity 추천
194
+ categories+=("productivity")
195
+
196
+ printf '%s\n' "${categories[@]}" | jq -R -s -c 'split("\n") | map(select(. != "")) | unique'
197
+ }
198
+
199
+ # 의존성 기반 플러그인 추천
200
+ recommend_by_deps() {
201
+ local deps=$(get_dependencies)
202
+
203
+ # 의존성 → 플러그인 매핑
204
+ local recommendations=()
205
+
206
+ echo "$deps" | jq -r '.[]' 2>/dev/null | while read dep; do
207
+ case "$dep" in
208
+ typescript|@types/*) echo "typescript-lsp" ;;
209
+ react|react-dom) echo "react-devtools" ;;
210
+ eslint) echo "eslint-fix" ;;
211
+ prettier) echo "prettier-format" ;;
212
+ jest|vitest) echo "test-runner" ;;
213
+ prisma) echo "prisma-helper" ;;
214
+ docker*) echo "docker-helper" ;;
215
+ @sentry/*|sentry) echo "sentry" ;;
216
+ @slack/*|slack*) echo "slack" ;;
217
+ graphql) echo "graphql-helper" ;;
218
+ tailwindcss) echo "tailwind-assist" ;;
219
+ esac
220
+ done | sort -u | jq -R -s -c 'split("\n") | map(select(. != ""))'
221
+ }
222
+
223
+ # 전체 프로젝트 분석 (JSON 출력)
224
+ full_analysis() {
225
+ local types=$(detect_project_type)
226
+ local frameworks=$(detect_frameworks)
227
+ local deps=$(get_dependencies)
228
+ local complexity=$(analyze_complexity)
229
+ local suggested=$(suggest_categories)
230
+ local recommended=$(recommend_by_deps)
231
+
232
+ cat <<EOF
233
+ {
234
+ "projectRoot": "$PROJECT_ROOT",
235
+ "analyzedAt": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
236
+ "projectTypes": $types,
237
+ "frameworks": $frameworks,
238
+ "dependencies": $deps,
239
+ "complexity": $complexity,
240
+ "suggestedCategories": $suggested,
241
+ "recommendedPlugins": $recommended
242
+ }
243
+ EOF
244
+ }
245
+
246
+ # CLI 인터페이스
247
+ case "$1" in
248
+ types)
249
+ detect_project_type
250
+ ;;
251
+ frameworks)
252
+ detect_frameworks
253
+ ;;
254
+ deps|dependencies)
255
+ get_dependencies
256
+ ;;
257
+ complexity)
258
+ analyze_complexity
259
+ ;;
260
+ suggest)
261
+ suggest_categories
262
+ ;;
263
+ recommend)
264
+ recommend_by_deps
265
+ ;;
266
+ full|analyze)
267
+ full_analysis
268
+ ;;
269
+ *)
270
+ echo "Usage: $0 {types|frameworks|deps|complexity|suggest|recommend|full}"
271
+ echo ""
272
+ echo "Commands:"
273
+ echo " types - Detect project types (nodejs, typescript, python, etc.)"
274
+ echo " frameworks - Detect frameworks (react, express, jest, etc.)"
275
+ echo " deps - Extract dependencies"
276
+ echo " complexity - Analyze project size/complexity"
277
+ echo " suggest - Suggest plugin categories"
278
+ echo " recommend - Recommend plugins based on dependencies"
279
+ echo " full - Full analysis (JSON)"
280
+ ;;
281
+ esac
@@ -0,0 +1,290 @@
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
+ SESSION_FILE="$DATA_DIR/.session"
9
+ RECOMMENDATION_LOG="$DATA_DIR/.recommendations"
10
+
11
+ # 무음 모드 설정
12
+ set_quiet_mode() {
13
+ local mode="$1" # on, off, toggle
14
+
15
+ if [ ! -f "$HISTORY_FILE" ]; then
16
+ echo "History file not found"
17
+ return 1
18
+ fi
19
+
20
+ if ! command -v jq &> /dev/null; then
21
+ echo "jq required"
22
+ return 1
23
+ fi
24
+
25
+ local current=$(jq -r '.preferences.quietMode // false' "$HISTORY_FILE")
26
+
27
+ case "$mode" in
28
+ on)
29
+ jq '.preferences.quietMode = true' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
30
+ echo "Quiet mode enabled. No recommendations will be shown."
31
+ ;;
32
+ off)
33
+ jq '.preferences.quietMode = false' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
34
+ echo "Quiet mode disabled. Recommendations will be shown."
35
+ ;;
36
+ toggle)
37
+ if [ "$current" = "true" ]; then
38
+ jq '.preferences.quietMode = false' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
39
+ echo "Quiet mode disabled."
40
+ else
41
+ jq '.preferences.quietMode = true' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
42
+ echo "Quiet mode enabled."
43
+ fi
44
+ ;;
45
+ status)
46
+ if [ "$current" = "true" ]; then
47
+ echo "on"
48
+ else
49
+ echo "off"
50
+ fi
51
+ ;;
52
+ esac
53
+ }
54
+
55
+ # 추천 가능 여부 확인 (빈도 제한)
56
+ can_show_recommendation() {
57
+ local limit_type="${1:-session}" # session, daily, weekly
58
+
59
+ # 무음 모드 확인
60
+ if [ -f "$HISTORY_FILE" ] && command -v jq &> /dev/null; then
61
+ local quiet=$(jq -r '.preferences.quietMode // false' "$HISTORY_FILE")
62
+ if [ "$quiet" = "true" ]; then
63
+ echo "blocked:quiet_mode"
64
+ return
65
+ fi
66
+ fi
67
+
68
+ # 추천 로그 확인
69
+ if [ ! -f "$RECOMMENDATION_LOG" ]; then
70
+ echo "true"
71
+ return
72
+ fi
73
+
74
+ local today=$(date +"%Y-%m-%d")
75
+ local session_id=""
76
+ if [ -f "$SESSION_FILE" ]; then
77
+ session_id=$(jq -r '.sessionId // ""' "$SESSION_FILE" 2>/dev/null)
78
+ fi
79
+
80
+ case "$limit_type" in
81
+ session)
82
+ # 세션당 1회 제한
83
+ local session_count=$(grep -c "session:$session_id" "$RECOMMENDATION_LOG" 2>/dev/null || echo "0")
84
+ local max_per_session=$(jq -r '.preferences.maxRecommendationsPerSession // 1' "$HISTORY_FILE" 2>/dev/null || echo "1")
85
+ if [ "$session_count" -ge "$max_per_session" ]; then
86
+ echo "blocked:session_limit"
87
+ return
88
+ fi
89
+ ;;
90
+ daily)
91
+ # 하루 N회 제한
92
+ local daily_count=$(grep -c "date:$today" "$RECOMMENDATION_LOG" 2>/dev/null || echo "0")
93
+ local max_per_day=$(jq -r '.preferences.maxRecommendationsPerDay // 3' "$HISTORY_FILE" 2>/dev/null || echo "3")
94
+ if [ "$daily_count" -ge "$max_per_day" ]; then
95
+ echo "blocked:daily_limit"
96
+ return
97
+ fi
98
+ ;;
99
+ esac
100
+
101
+ echo "true"
102
+ }
103
+
104
+ # 추천 기록
105
+ record_recommendation() {
106
+ local plugin_count="$1"
107
+ local trigger="$2" # post-task, manual, auto
108
+
109
+ local today=$(date +"%Y-%m-%d")
110
+ local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
111
+ local session_id=""
112
+
113
+ if [ -f "$SESSION_FILE" ]; then
114
+ session_id=$(jq -r '.sessionId // ""' "$SESSION_FILE" 2>/dev/null)
115
+ fi
116
+
117
+ echo "date:$today session:$session_id time:$timestamp count:$plugin_count trigger:$trigger" >> "$RECOMMENDATION_LOG"
118
+ }
119
+
120
+ # 스마트 타이밍 체크
121
+ check_timing() {
122
+ local event="$1" # commit, pr, task-complete
123
+
124
+ if [ ! -f "$HISTORY_FILE" ] || ! command -v jq &> /dev/null; then
125
+ echo "true"
126
+ return
127
+ fi
128
+
129
+ local smart_timing=$(jq -r '.preferences.smartTiming // {}' "$HISTORY_FILE")
130
+ local only_after_commit=$(echo "$smart_timing" | jq -r '.onlyAfterCommit // false')
131
+ local only_after_pr=$(echo "$smart_timing" | jq -r '.onlyAfterPR // false')
132
+
133
+ # 특정 이벤트 후에만 추천하도록 설정된 경우
134
+ if [ "$only_after_commit" = "true" ] && [ "$event" != "commit" ]; then
135
+ echo "blocked:not_after_commit"
136
+ return
137
+ fi
138
+
139
+ if [ "$only_after_pr" = "true" ] && [ "$event" != "pr" ]; then
140
+ echo "blocked:not_after_pr"
141
+ return
142
+ fi
143
+
144
+ echo "true"
145
+ }
146
+
147
+ # 미니 모드 출력
148
+ show_mini_notification() {
149
+ local count="$1"
150
+
151
+ if [ "$count" -eq 0 ]; then
152
+ return
153
+ fi
154
+
155
+ echo "💡 $count개의 플러그인 추천이 있습니다. \`/scout\`로 확인하세요."
156
+ }
157
+
158
+ # 추천 빈도 설정
159
+ set_frequency() {
160
+ local key="$1"
161
+ local value="$2"
162
+
163
+ if [ ! -f "$HISTORY_FILE" ] || ! command -v jq &> /dev/null; then
164
+ echo "Error: history file or jq not found"
165
+ return 1
166
+ fi
167
+
168
+ case "$key" in
169
+ session)
170
+ jq --argjson val "$value" '.preferences.maxRecommendationsPerSession = $val' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
171
+ echo "Max recommendations per session: $value"
172
+ ;;
173
+ daily)
174
+ jq --argjson val "$value" '.preferences.maxRecommendationsPerDay = $val' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
175
+ echo "Max recommendations per day: $value"
176
+ ;;
177
+ cooldown)
178
+ jq --argjson val "$value" '.preferences.recommendationCooldown = $val' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
179
+ echo "Recommendation cooldown: $value minutes"
180
+ ;;
181
+ esac
182
+ }
183
+
184
+ # 스마트 타이밍 설정
185
+ set_smart_timing() {
186
+ local key="$1"
187
+ local value="$2"
188
+
189
+ if [ ! -f "$HISTORY_FILE" ] || ! command -v jq &> /dev/null; then
190
+ echo "Error: history file or jq not found"
191
+ return 1
192
+ fi
193
+
194
+ case "$key" in
195
+ after-commit)
196
+ jq --argjson val "$value" '.preferences.smartTiming.onlyAfterCommit = $val' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
197
+ echo "Only recommend after commit: $value"
198
+ ;;
199
+ after-pr)
200
+ jq --argjson val "$value" '.preferences.smartTiming.onlyAfterPR = $val' "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
201
+ echo "Only recommend after PR: $value"
202
+ ;;
203
+ esac
204
+ }
205
+
206
+ # 추천 로그 정리 (7일 이상 된 기록 삭제)
207
+ cleanup_logs() {
208
+ if [ ! -f "$RECOMMENDATION_LOG" ]; then
209
+ return
210
+ fi
211
+
212
+ local week_ago=$(date -v-7d +"%Y-%m-%d" 2>/dev/null || date -d "7 days ago" +"%Y-%m-%d" 2>/dev/null)
213
+
214
+ if [ -n "$week_ago" ]; then
215
+ grep -v "date:$week_ago" "$RECOMMENDATION_LOG" | grep -v "date:$(date -v-8d +%Y-%m-%d 2>/dev/null)" > "$RECOMMENDATION_LOG.tmp" 2>/dev/null
216
+ mv "$RECOMMENDATION_LOG.tmp" "$RECOMMENDATION_LOG" 2>/dev/null
217
+ fi
218
+ }
219
+
220
+ # 상태 요약
221
+ status() {
222
+ echo "=== Recommendation Controller Status ==="
223
+ echo ""
224
+
225
+ if [ -f "$HISTORY_FILE" ] && command -v jq &> /dev/null; then
226
+ local quiet=$(jq -r '.preferences.quietMode // false' "$HISTORY_FILE")
227
+ local max_session=$(jq -r '.preferences.maxRecommendationsPerSession // 1' "$HISTORY_FILE")
228
+ local max_daily=$(jq -r '.preferences.maxRecommendationsPerDay // 3' "$HISTORY_FILE")
229
+ local cooldown=$(jq -r '.preferences.recommendationCooldown // 30' "$HISTORY_FILE")
230
+
231
+ echo "Quiet Mode: $quiet"
232
+ echo "Max per Session: $max_session"
233
+ echo "Max per Day: $max_daily"
234
+ echo "Cooldown: $cooldown min"
235
+ echo ""
236
+
237
+ if [ -f "$RECOMMENDATION_LOG" ]; then
238
+ local today=$(date +"%Y-%m-%d")
239
+ local today_count=$(grep -c "date:$today" "$RECOMMENDATION_LOG" 2>/dev/null || echo "0")
240
+ echo "Today's Recommendations: $today_count"
241
+ fi
242
+ else
243
+ echo "Configuration not available"
244
+ fi
245
+ }
246
+
247
+ # CLI 인터페이스
248
+ case "$1" in
249
+ quiet)
250
+ set_quiet_mode "${2:-status}"
251
+ ;;
252
+ can-recommend)
253
+ can_show_recommendation "${2:-session}"
254
+ ;;
255
+ record)
256
+ record_recommendation "$2" "$3"
257
+ ;;
258
+ timing)
259
+ check_timing "$2"
260
+ ;;
261
+ mini)
262
+ show_mini_notification "$2"
263
+ ;;
264
+ frequency)
265
+ set_frequency "$2" "$3"
266
+ ;;
267
+ smart-timing)
268
+ set_smart_timing "$2" "$3"
269
+ ;;
270
+ cleanup)
271
+ cleanup_logs
272
+ ;;
273
+ status)
274
+ status
275
+ ;;
276
+ *)
277
+ echo "Usage: $0 {quiet|can-recommend|record|timing|mini|frequency|smart-timing|cleanup|status}"
278
+ echo ""
279
+ echo "Commands:"
280
+ echo " quiet [on|off|toggle|status] - Set quiet mode"
281
+ echo " can-recommend [session|daily] - Check if can show recommendation"
282
+ echo " record <count> <trigger> - Record a recommendation"
283
+ echo " timing <event> - Check smart timing"
284
+ echo " mini <count> - Show mini notification"
285
+ echo " frequency <session|daily> <value> - Set frequency limit"
286
+ echo " smart-timing <key> <value> - Set smart timing"
287
+ echo " cleanup - Clean old logs"
288
+ echo " status - Show status"
289
+ ;;
290
+ esac