timsquad 2.0.0 → 2.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 (56) hide show
  1. package/README.md +688 -154
  2. package/dist/commands/feedback.d.ts.map +1 -1
  3. package/dist/commands/feedback.js +21 -2
  4. package/dist/commands/feedback.js.map +1 -1
  5. package/dist/commands/git/commit.d.ts.map +1 -1
  6. package/dist/commands/git/commit.js +1 -4
  7. package/dist/commands/git/commit.js.map +1 -1
  8. package/dist/commands/improve.d.ts +3 -0
  9. package/dist/commands/improve.d.ts.map +1 -0
  10. package/dist/commands/improve.js +286 -0
  11. package/dist/commands/improve.js.map +1 -0
  12. package/dist/commands/init.js +3 -2
  13. package/dist/commands/init.js.map +1 -1
  14. package/dist/commands/log.d.ts.map +1 -1
  15. package/dist/commands/log.js +202 -2
  16. package/dist/commands/log.js.map +1 -1
  17. package/dist/commands/metrics.d.ts.map +1 -1
  18. package/dist/commands/metrics.js +404 -99
  19. package/dist/commands/metrics.js.map +1 -1
  20. package/dist/commands/retro.d.ts.map +1 -1
  21. package/dist/commands/retro.js +454 -54
  22. package/dist/commands/retro.js.map +1 -1
  23. package/dist/commands/session.d.ts +3 -0
  24. package/dist/commands/session.d.ts.map +1 -0
  25. package/dist/commands/session.js +346 -0
  26. package/dist/commands/session.js.map +1 -0
  27. package/dist/index.js +4 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/lib/template.d.ts.map +1 -1
  30. package/dist/lib/template.js +66 -1
  31. package/dist/lib/template.js.map +1 -1
  32. package/dist/types/feedback.d.ts +52 -1
  33. package/dist/types/feedback.d.ts.map +1 -1
  34. package/dist/types/feedback.js +0 -3
  35. package/dist/types/feedback.js.map +1 -1
  36. package/dist/types/project.d.ts +1 -1
  37. package/dist/types/project.d.ts.map +1 -1
  38. package/dist/types/project.js +3 -1
  39. package/dist/types/project.js.map +1 -1
  40. package/package.json +1 -1
  41. package/templates/common/claude/agents/tsq-dba.md +21 -0
  42. package/templates/common/claude/agents/tsq-designer.md +19 -0
  43. package/templates/common/claude/agents/tsq-developer.md +59 -0
  44. package/templates/common/claude/agents/tsq-planner.md +101 -1
  45. package/templates/common/claude/agents/tsq-prompter.md +20 -0
  46. package/templates/common/claude/agents/tsq-qa.md +38 -4
  47. package/templates/common/claude/agents/tsq-retro.md +25 -0
  48. package/templates/common/claude/agents/tsq-security.md +31 -0
  49. package/templates/common/claude/hooks/auto-metrics.sh +165 -0
  50. package/templates/common/claude/hooks/auto-worklog.sh +245 -0
  51. package/templates/common/claude/hooks/event-logger.sh +208 -0
  52. package/templates/common/claude/settings.json +86 -0
  53. package/templates/common/config.template.yaml +2 -1
  54. package/templates/common/timsquad/process/phase-checklist.yaml +174 -0
  55. package/templates/common/timsquad/process/state-machine.xml +12 -0
  56. package/templates/common/timsquad/process/workflow-base.xml +124 -0
@@ -0,0 +1,165 @@
1
+ #!/bin/bash
2
+ # TimSquad Auto Metrics Collector
3
+ # SessionEnd 이벤트 시 호출되어 세션 JSONL을 분석하고
4
+ # .timsquad/retrospective/metrics/latest.json 에 누적 메트릭 갱신
5
+ #
6
+ # 토큰 비용: 0 (순수 파일 I/O)
7
+ # 사용법: auto-metrics.sh <jsonl_file> <session_id_short>
8
+
9
+ set -euo pipefail
10
+
11
+ JSONL_FILE="${1:-}"
12
+ SESSION_SHORT="${2:-unknown}"
13
+
14
+ if [ -z "$JSONL_FILE" ] || [ ! -f "$JSONL_FILE" ]; then
15
+ exit 0
16
+ fi
17
+
18
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
19
+ METRICS_DIR="$PROJECT_DIR/.timsquad/retrospective/metrics"
20
+ mkdir -p "$METRICS_DIR"
21
+
22
+ LATEST_FILE="$METRICS_DIR/latest.json"
23
+ TODAY=$(date +"%Y-%m-%d")
24
+ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
25
+
26
+ # ============================================================
27
+ # JSONL에서 세션 통계 추출
28
+ # ============================================================
29
+
30
+ TOTAL_EVENTS=$(wc -l < "$JSONL_FILE" | tr -d ' ')
31
+
32
+ if [ "$TOTAL_EVENTS" -lt 3 ]; then
33
+ exit 0
34
+ fi
35
+
36
+ # 도구 사용
37
+ TOOL_USES=$(jq -r 'select(.event == "PostToolUse")' "$JSONL_FILE" 2>/dev/null | wc -l | tr -d ' ')
38
+ TOOL_FAILURES=$(jq -r 'select(.event == "PostToolUseFailure")' "$JSONL_FILE" 2>/dev/null | wc -l | tr -d ' ')
39
+ SUBAGENTS=$(jq -r 'select(.event == "SubagentStart")' "$JSONL_FILE" 2>/dev/null | wc -l | tr -d ' ')
40
+
41
+ # 토큰 (SessionEnd에서 추출)
42
+ TOKEN_DATA=$(jq -c 'select(.event == "SessionEnd") | .total_usage // {}' "$JSONL_FILE" 2>/dev/null | head -1)
43
+ if [ -z "$TOKEN_DATA" ] || [ "$TOKEN_DATA" = "{}" ]; then
44
+ TOKEN_INPUT=0
45
+ TOKEN_OUTPUT=0
46
+ TOKEN_CACHE_CREATE=0
47
+ TOKEN_CACHE_READ=0
48
+ else
49
+ TOKEN_INPUT=$(echo "$TOKEN_DATA" | jq -r '.total_input // 0')
50
+ TOKEN_OUTPUT=$(echo "$TOKEN_DATA" | jq -r '.total_output // 0')
51
+ TOKEN_CACHE_CREATE=$(echo "$TOKEN_DATA" | jq -r '.total_cache_create // 0')
52
+ TOKEN_CACHE_READ=$(echo "$TOKEN_DATA" | jq -r '.total_cache_read // 0')
53
+ fi
54
+
55
+ # CLI 채택률
56
+ TOTAL_BASH=$(jq -r 'select(.event == "PostToolUse" and .tool == "Bash") | .detail.command // ""' "$JSONL_FILE" 2>/dev/null | wc -l | tr -d ' ')
57
+ TSQ_BASH=$(jq -r 'select(.event == "PostToolUse" and .tool == "Bash") | .detail.command // ""' "$JSONL_FILE" 2>/dev/null | grep -c '^\(tsq\|npx tsq\)' || echo "0")
58
+
59
+ # ============================================================
60
+ # latest.json 읽기 또는 초기화
61
+ # ============================================================
62
+
63
+ if [ -f "$LATEST_FILE" ]; then
64
+ EXISTING=$(cat "$LATEST_FILE")
65
+ else
66
+ EXISTING='{}'
67
+ fi
68
+
69
+ # 기존 값 추출 (없으면 0)
70
+ PREV_SESSIONS=$(echo "$EXISTING" | jq -r '.totalSessions // 0')
71
+ PREV_EVENTS=$(echo "$EXISTING" | jq -r '.totalEvents // 0')
72
+ PREV_TOOL_USES=$(echo "$EXISTING" | jq -r '.totalToolUses // 0')
73
+ PREV_FAILURES=$(echo "$EXISTING" | jq -r '.totalFailures // 0')
74
+ PREV_SUBAGENTS=$(echo "$EXISTING" | jq -r '.subagentCount // 0')
75
+ PREV_INPUT=$(echo "$EXISTING" | jq -r '.tokens.totalInput // 0')
76
+ PREV_OUTPUT=$(echo "$EXISTING" | jq -r '.tokens.totalOutput // 0')
77
+ PREV_CACHE_CREATE=$(echo "$EXISTING" | jq -r '.tokens.totalCacheCreate // 0')
78
+ PREV_CACHE_READ=$(echo "$EXISTING" | jq -r '.tokens.totalCacheRead // 0')
79
+ PREV_BASH=$(echo "$EXISTING" | jq -r '.cliAdoption.totalBashCommands // 0')
80
+ PREV_TSQ=$(echo "$EXISTING" | jq -r '.cliAdoption.tsqCommands // 0')
81
+
82
+ # ============================================================
83
+ # 누적 갱신
84
+ # ============================================================
85
+
86
+ NEW_SESSIONS=$((PREV_SESSIONS + 1))
87
+ NEW_EVENTS=$((PREV_EVENTS + TOTAL_EVENTS))
88
+ NEW_TOOL_USES=$((PREV_TOOL_USES + TOOL_USES))
89
+ NEW_FAILURES=$((PREV_FAILURES + TOOL_FAILURES))
90
+ NEW_SUBAGENTS=$((PREV_SUBAGENTS + SUBAGENTS))
91
+ NEW_INPUT=$((PREV_INPUT + TOKEN_INPUT))
92
+ NEW_OUTPUT=$((PREV_OUTPUT + TOKEN_OUTPUT))
93
+ NEW_CACHE_CREATE=$((PREV_CACHE_CREATE + TOKEN_CACHE_CREATE))
94
+ NEW_CACHE_READ=$((PREV_CACHE_READ + TOKEN_CACHE_READ))
95
+ NEW_BASH=$((PREV_BASH + TOTAL_BASH))
96
+ NEW_TSQ=$((PREV_TSQ + TSQ_BASH))
97
+
98
+ # 파생 지표
99
+ TOTAL_ATTEMPTS=$((NEW_TOOL_USES + NEW_FAILURES))
100
+ if [ "$TOTAL_ATTEMPTS" -gt 0 ]; then
101
+ TOOL_EFFICIENCY=$((NEW_TOOL_USES * 100 / TOTAL_ATTEMPTS))
102
+ else
103
+ TOOL_EFFICIENCY=0
104
+ fi
105
+
106
+ ALL_INPUT=$((NEW_INPUT + NEW_CACHE_CREATE + NEW_CACHE_READ))
107
+ if [ "$ALL_INPUT" -gt 0 ]; then
108
+ CACHE_HIT_RATE=$((NEW_CACHE_READ * 100 / ALL_INPUT))
109
+ else
110
+ CACHE_HIT_RATE=0
111
+ fi
112
+
113
+ if [ "$NEW_BASH" -gt 0 ]; then
114
+ CLI_ADOPTION=$((NEW_TSQ * 100 / NEW_BASH))
115
+ else
116
+ CLI_ADOPTION=0
117
+ fi
118
+
119
+ # ============================================================
120
+ # latest.json 갱신
121
+ # ============================================================
122
+
123
+ jq -n -c \
124
+ --arg ts "$TIMESTAMP" \
125
+ --arg date "$TODAY" \
126
+ --arg last_session "$SESSION_SHORT" \
127
+ --argjson sessions "$NEW_SESSIONS" \
128
+ --argjson events "$NEW_EVENTS" \
129
+ --argjson tool_uses "$NEW_TOOL_USES" \
130
+ --argjson failures "$NEW_FAILURES" \
131
+ --argjson efficiency "$TOOL_EFFICIENCY" \
132
+ --argjson subagents "$NEW_SUBAGENTS" \
133
+ --argjson input "$NEW_INPUT" \
134
+ --argjson output "$NEW_OUTPUT" \
135
+ --argjson cache_create "$NEW_CACHE_CREATE" \
136
+ --argjson cache_read "$NEW_CACHE_READ" \
137
+ --argjson cache_rate "$CACHE_HIT_RATE" \
138
+ --argjson bash_total "$NEW_BASH" \
139
+ --argjson tsq_total "$NEW_TSQ" \
140
+ --argjson cli_rate "$CLI_ADOPTION" \
141
+ '{
142
+ updatedAt: $ts,
143
+ lastDate: $date,
144
+ lastSession: $last_session,
145
+ totalSessions: $sessions,
146
+ totalEvents: $events,
147
+ totalToolUses: $tool_uses,
148
+ totalFailures: $failures,
149
+ toolEfficiency: $efficiency,
150
+ subagentCount: $subagents,
151
+ tokens: {
152
+ totalInput: $input,
153
+ totalOutput: $output,
154
+ totalCacheCreate: $cache_create,
155
+ totalCacheRead: $cache_read,
156
+ cacheHitRate: $cache_rate
157
+ },
158
+ cliAdoption: {
159
+ totalBashCommands: $bash_total,
160
+ tsqCommands: $tsq_total,
161
+ adoptionRate: $cli_rate
162
+ }
163
+ }' | jq '.' > "$LATEST_FILE"
164
+
165
+ exit 0
@@ -0,0 +1,245 @@
1
+ #!/bin/bash
2
+ # TimSquad Auto Work Log Generator
3
+ # SessionEnd 이벤트 시 호출되어 세션 JSONL을 분석하고
4
+ # .timsquad/logs/{date}-session.md 작업 로그를 자동 생성
5
+ #
6
+ # 사용법: auto-worklog.sh <jsonl_file> <session_id_short>
7
+
8
+ set -euo pipefail
9
+
10
+ JSONL_FILE="${1:-}"
11
+ SESSION_SHORT="${2:-unknown}"
12
+
13
+ if [ -z "$JSONL_FILE" ] || [ ! -f "$JSONL_FILE" ]; then
14
+ exit 0
15
+ fi
16
+
17
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
18
+ LOGS_DIR="$PROJECT_DIR/.timsquad/logs"
19
+ mkdir -p "$LOGS_DIR"
20
+
21
+ TODAY=$(date +"%Y-%m-%d")
22
+ LOG_FILE="$LOGS_DIR/${TODAY}-session.md"
23
+ TIMESTAMP=$(date +"%H:%M:%S")
24
+
25
+ # ============================================================
26
+ # JSONL에서 통계 추출
27
+ # ============================================================
28
+
29
+ TOTAL_EVENTS=$(wc -l < "$JSONL_FILE" | tr -d ' ')
30
+
31
+ # 최소 이벤트 수 체크 (너무 짧은 세션은 스킵)
32
+ if [ "$TOTAL_EVENTS" -lt 3 ]; then
33
+ exit 0
34
+ fi
35
+
36
+ # 도구 사용 횟수
37
+ TOOL_EVENTS=$(jq -r 'select(.event == "PostToolUse") | .tool' "$JSONL_FILE" 2>/dev/null | wc -l | tr -d ' ')
38
+ FAILURES=$(jq -r 'select(.event == "PostToolUseFailure")' "$JSONL_FILE" 2>/dev/null | wc -l | tr -d ' ')
39
+
40
+ # 서브에이전트 사용
41
+ SUBAGENT_STARTS=$(jq -r 'select(.event == "SubagentStart") | .detail.subagent_type' "$JSONL_FILE" 2>/dev/null)
42
+ SUBAGENT_COUNT=$(echo "$SUBAGENT_STARTS" | grep -c '.' 2>/dev/null || echo "0")
43
+
44
+ # 도구별 횟수 (상위 5개)
45
+ TOOL_BREAKDOWN=$(jq -r 'select(.event == "PostToolUse") | .tool' "$JSONL_FILE" 2>/dev/null \
46
+ | sort | uniq -c | sort -rn | head -5 \
47
+ | awk '{printf "%s(%d) ", $2, $1}')
48
+
49
+ # 수정된 파일 목록
50
+ FILES_MODIFIED=$(jq -r 'select(.event == "PostToolUse" and (.tool == "Write" or .tool == "Edit")) | .detail.file_path // empty' "$JSONL_FILE" 2>/dev/null \
51
+ | sort -u)
52
+
53
+ # 읽은 파일 목록 (상위 10개)
54
+ FILES_READ=$(jq -r 'select(.event == "PostToolUse" and .tool == "Read") | .detail.file_path // empty' "$JSONL_FILE" 2>/dev/null \
55
+ | sort -u | head -10)
56
+
57
+ # Bash 커맨드 (상위 5개)
58
+ BASH_COMMANDS=$(jq -r 'select(.event == "PostToolUse" and .tool == "Bash") | .detail.command // empty' "$JSONL_FILE" 2>/dev/null \
59
+ | head -5)
60
+
61
+ # 서브에이전트 상세
62
+ SUBAGENT_DETAILS=$(jq -r 'select(.event == "SubagentStart") | "\(.detail.subagent_type): \(.detail.description // "")"' "$JSONL_FILE" 2>/dev/null)
63
+
64
+ # 타임라인
65
+ FIRST_TS=$(jq -r '.timestamp' "$JSONL_FILE" 2>/dev/null | head -1)
66
+ LAST_TS=$(jq -r '.timestamp' "$JSONL_FILE" 2>/dev/null | tail -1)
67
+
68
+ FIRST_TIME=$(echo "$FIRST_TS" | sed 's/.*T//;s/Z//')
69
+ LAST_TIME=$(echo "$LAST_TS" | sed 's/.*T//;s/Z//')
70
+
71
+ # ============================================================
72
+ # 작업 로그 생성
73
+ # ============================================================
74
+
75
+ # 파일이 없으면 헤더 생성
76
+ if [ ! -f "$LOG_FILE" ]; then
77
+ cat > "$LOG_FILE" << EOF
78
+ # session 작업 로그 - ${TODAY}
79
+
80
+ > TimSquad Auto Work Log - Claude Code 세션 이벤트에서 자동 생성
81
+
82
+ ---
83
+
84
+ EOF
85
+ fi
86
+
87
+ # 로그 항목 추가
88
+ {
89
+ echo ""
90
+ echo "## [$TIMESTAMP] work"
91
+ echo ""
92
+ echo "**Session:** \`$SESSION_SHORT\` ($FIRST_TIME ~ $LAST_TIME)"
93
+ echo ""
94
+ echo "### 세션 통계"
95
+ echo "- 총 이벤트: $TOTAL_EVENTS"
96
+ echo "- 도구 사용: $TOOL_EVENTS회"
97
+ if [ "$FAILURES" -gt 0 ]; then
98
+ echo "- 실패: ${FAILURES}회"
99
+ fi
100
+ if [ "$SUBAGENT_COUNT" -gt 0 ]; then
101
+ echo "- 서브에이전트: ${SUBAGENT_COUNT}회"
102
+ fi
103
+ echo "- 도구 분포: $TOOL_BREAKDOWN"
104
+ echo ""
105
+
106
+ # 수정 파일
107
+ if [ -n "$FILES_MODIFIED" ]; then
108
+ echo "### 수정된 파일"
109
+ echo "$FILES_MODIFIED" | while read -r f; do
110
+ [ -n "$f" ] && echo "- \`$f\`"
111
+ done
112
+ echo ""
113
+ fi
114
+
115
+ # 서브에이전트 활동
116
+ if [ -n "$SUBAGENT_DETAILS" ] && [ "$SUBAGENT_COUNT" -gt 0 ]; then
117
+ echo "### 서브에이전트 활동"
118
+ echo "$SUBAGENT_DETAILS" | while read -r line; do
119
+ [ -n "$line" ] && echo "- $line"
120
+ done
121
+ echo ""
122
+ fi
123
+
124
+ # 주요 Bash 명령
125
+ if [ -n "$BASH_COMMANDS" ]; then
126
+ echo "### 주요 명령"
127
+ echo '```'
128
+ echo "$BASH_COMMANDS"
129
+ echo '```'
130
+ echo ""
131
+ fi
132
+
133
+ # 토큰 사용량 (SessionEnd 이벤트에서 추출)
134
+ SESSION_END_USAGE=$(jq -c 'select(.event == "SessionEnd") | .total_usage // empty' "$JSONL_FILE" 2>/dev/null | head -1)
135
+
136
+ if [ -n "$SESSION_END_USAGE" ] && [ "$SESSION_END_USAGE" != "{}" ]; then
137
+ TOTAL_INPUT=$(echo "$SESSION_END_USAGE" | jq -r '.total_input // 0')
138
+ TOTAL_OUTPUT=$(echo "$SESSION_END_USAGE" | jq -r '.total_output // 0')
139
+ TOTAL_CACHE_CREATE=$(echo "$SESSION_END_USAGE" | jq -r '.total_cache_create // 0')
140
+ TOTAL_CACHE_READ=$(echo "$SESSION_END_USAGE" | jq -r '.total_cache_read // 0')
141
+ TOTAL_TURNS=$(echo "$SESSION_END_USAGE" | jq -r '.turns // 0')
142
+
143
+ # cache hit rate 계산
144
+ TOTAL_ALL_INPUT=$((TOTAL_INPUT + TOTAL_CACHE_CREATE + TOTAL_CACHE_READ))
145
+ if [ "$TOTAL_ALL_INPUT" -gt 0 ]; then
146
+ CACHE_HIT_RATE=$((TOTAL_CACHE_READ * 100 / TOTAL_ALL_INPUT))
147
+ else
148
+ CACHE_HIT_RATE=0
149
+ fi
150
+
151
+ echo "### 토큰 사용량"
152
+ echo ""
153
+ echo "| 항목 | 토큰 | 의미 |"
154
+ echo "|------|------|------|"
155
+ echo "| Input | $(printf "%'d" "$TOTAL_INPUT") | 새로 처리된 입력 (캐시 미스) |"
156
+ echo "| Output | $(printf "%'d" "$TOTAL_OUTPUT") | 생성된 출력 (비용 주요 부분) |"
157
+ echo "| Cache Create | $(printf "%'d" "$TOTAL_CACHE_CREATE") | 새로 캐시 저장 (첫 턴에 높음) |"
158
+ echo "| Cache Read | $(printf "%'d" "$TOTAL_CACHE_READ") | 캐시 재사용 (높을수록 효율적) |"
159
+ echo "| **Cache Hit Rate** | **${CACHE_HIT_RATE}%** | **80%+ 우수 / 60-80% 보통 / <60% 주의** |"
160
+ echo "| Turns | $TOTAL_TURNS | API 호출 횟수 |"
161
+ echo ""
162
+ fi
163
+
164
+ } >> "$LOG_FILE"
165
+
166
+ # ============================================================
167
+ # 품질 경고 (threshold 기반 자동 감지)
168
+ # 토큰 비용: 0 - 순수 조건 체크
169
+ # ============================================================
170
+
171
+ ALERT_FILE="$LOGS_DIR/${TODAY}-alerts.md"
172
+ ALERTS_FOUND=0
173
+
174
+ # Alert 1: 도구 실패율 > 10%
175
+ TOTAL_ATTEMPTS=$((TOOL_EVENTS + FAILURES))
176
+ if [ "$TOTAL_ATTEMPTS" -gt 5 ]; then
177
+ FAIL_RATE=$((FAILURES * 100 / TOTAL_ATTEMPTS))
178
+ if [ "$FAIL_RATE" -gt 10 ]; then
179
+ if [ "$ALERTS_FOUND" -eq 0 ] && [ ! -f "$ALERT_FILE" ]; then
180
+ echo "# Quality Alerts - ${TODAY}" > "$ALERT_FILE"
181
+ echo "" >> "$ALERT_FILE"
182
+ echo "> TimSquad 자동 품질 경고 - threshold 초과 시 자동 생성" >> "$ALERT_FILE"
183
+ echo "" >> "$ALERT_FILE"
184
+ fi
185
+ echo "- **[$TIMESTAMP]** Tool Failure Rate ${FAIL_RATE}% (> 10%) - 세션 \`$SESSION_SHORT\`" >> "$ALERT_FILE"
186
+ echo " - 도구 실패 ${FAILURES}/${TOTAL_ATTEMPTS}회. 에이전트 프롬프트나 권한 설정 점검" >> "$ALERT_FILE"
187
+ ALERTS_FOUND=1
188
+ fi
189
+ fi
190
+
191
+ # Alert 2: Cache Hit Rate < 60% (토큰 데이터가 있을 때만)
192
+ if [ -n "$SESSION_END_USAGE" ] && [ "$SESSION_END_USAGE" != "{}" ]; then
193
+ if [ "$TOTAL_ALL_INPUT" -gt 0 ] && [ "$CACHE_HIT_RATE" -lt 60 ]; then
194
+ if [ "$ALERTS_FOUND" -eq 0 ] && [ ! -f "$ALERT_FILE" ]; then
195
+ echo "# Quality Alerts - ${TODAY}" > "$ALERT_FILE"
196
+ echo "" >> "$ALERT_FILE"
197
+ echo "> TimSquad 자동 품질 경고 - threshold 초과 시 자동 생성" >> "$ALERT_FILE"
198
+ echo "" >> "$ALERT_FILE"
199
+ fi
200
+ echo "- **[$TIMESTAMP]** Cache Hit Rate ${CACHE_HIT_RATE}% (< 60%) - 세션 \`$SESSION_SHORT\`" >> "$ALERT_FILE"
201
+ echo " - 프롬프트 구조 불안정. CLAUDE.md 또는 에이전트 프롬프트 검토" >> "$ALERT_FILE"
202
+ ALERTS_FOUND=1
203
+ fi
204
+ fi
205
+
206
+ # ============================================================
207
+ # 자동 피드백 생성 (실패 패턴 감지)
208
+ # 도구 실패 3회 이상 → 자동으로 FB-XXXX.json 생성
209
+ # 토큰 비용: 0
210
+ # ============================================================
211
+
212
+ if [ "$FAILURES" -ge 3 ]; then
213
+ FEEDBACK_DIR="$PROJECT_DIR/.timsquad/feedback"
214
+ mkdir -p "$FEEDBACK_DIR"
215
+
216
+ # 다음 피드백 번호 결정
217
+ EXISTING_COUNT=$(ls "$FEEDBACK_DIR"/FB-*.json 2>/dev/null | wc -l | tr -d ' ')
218
+ NEXT_NUM=$((EXISTING_COUNT + 1))
219
+ FB_ID=$(printf "FB-%04d" "$NEXT_NUM")
220
+ FB_FILE="$FEEDBACK_DIR/${FB_ID}.json"
221
+
222
+ # 실패한 도구 목록 추출
223
+ FAILED_TOOLS=$(jq -r 'select(.event == "PostToolUseFailure") | .tool // "unknown"' "$JSONL_FILE" 2>/dev/null \
224
+ | sort | uniq -c | sort -rn | head -3 \
225
+ | awk '{printf "%s(%d) ", $2, $1}')
226
+
227
+ jq -n \
228
+ --arg id "$FB_ID" \
229
+ --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
230
+ --arg msg "세션 ${SESSION_SHORT}에서 도구 실패 ${FAILURES}회 감지. 실패 도구: ${FAILED_TOOLS}" \
231
+ --arg trigger "tool_failure" \
232
+ --arg session "$SESSION_SHORT" \
233
+ '{
234
+ id: $id,
235
+ timestamp: $ts,
236
+ type: "auto-feedback",
237
+ level: 1,
238
+ trigger: $trigger,
239
+ message: $msg,
240
+ routeTo: "developer",
241
+ tags: ["auto-detected", "tool_failure", $session]
242
+ }' > "$FB_FILE"
243
+ fi
244
+
245
+ exit 0
@@ -0,0 +1,208 @@
1
+ #!/bin/bash
2
+ # TimSquad Event Logger - Claude Code Hook
3
+ # PostToolUse, Stop, SessionStart, SessionEnd 이벤트를 자동 기록
4
+ #
5
+ # 저장 위치: .timsquad/logs/sessions/{date}-{session_id_short}.jsonl
6
+ # 형식: JSON Lines (한 줄 = 하나의 이벤트)
7
+
8
+ set -euo pipefail
9
+
10
+ # 프로젝트 루트 결정
11
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
12
+ SESSIONS_DIR="$PROJECT_DIR/.timsquad/logs/sessions"
13
+ mkdir -p "$SESSIONS_DIR"
14
+
15
+ # stdin에서 JSON 읽기
16
+ INPUT=$(cat)
17
+
18
+ # 기본 필드 추출
19
+ EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // "unknown"')
20
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
21
+ SESSION_SHORT="${SESSION_ID:0:8}"
22
+ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
23
+ TODAY=$(date +"%Y-%m-%d")
24
+
25
+ # 로그 파일 경로
26
+ LOG_FILE="$SESSIONS_DIR/${TODAY}-${SESSION_SHORT}.jsonl"
27
+
28
+ # 이벤트별 데이터 추출
29
+ case "$EVENT" in
30
+ PostToolUse|PostToolUseFailure)
31
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "unknown"')
32
+
33
+ # tool_input에서 핵심 정보만 추출 (전체 content 제외 - 용량 절약)
34
+ case "$TOOL_NAME" in
35
+ Write|Edit)
36
+ TOOL_SUMMARY=$(echo "$INPUT" | jq -c '{
37
+ file_path: .tool_input.file_path,
38
+ action: "'"$TOOL_NAME"'"
39
+ }')
40
+ ;;
41
+ Read)
42
+ TOOL_SUMMARY=$(echo "$INPUT" | jq -c '{
43
+ file_path: .tool_input.file_path,
44
+ action: "Read"
45
+ }')
46
+ ;;
47
+ Bash)
48
+ # 커맨드만 추출 (출력 제외)
49
+ TOOL_SUMMARY=$(echo "$INPUT" | jq -c '{
50
+ command: (.tool_input.command // "" | .[0:200]),
51
+ action: "Bash"
52
+ }')
53
+ ;;
54
+ Glob|Grep)
55
+ TOOL_SUMMARY=$(echo "$INPUT" | jq -c '{
56
+ pattern: (.tool_input.pattern // ""),
57
+ path: (.tool_input.path // ""),
58
+ action: "'"$TOOL_NAME"'"
59
+ }')
60
+ ;;
61
+ Task)
62
+ TOOL_SUMMARY=$(echo "$INPUT" | jq -c '{
63
+ description: (.tool_input.description // ""),
64
+ subagent_type: (.tool_input.subagent_type // ""),
65
+ action: "Task"
66
+ }')
67
+ ;;
68
+ *)
69
+ TOOL_SUMMARY=$(echo "$INPUT" | jq -c '{action: "'"$TOOL_NAME"'"}')
70
+ ;;
71
+ esac
72
+
73
+ # 성공 여부
74
+ if [ "$EVENT" = "PostToolUseFailure" ]; then
75
+ STATUS="failure"
76
+ else
77
+ STATUS="success"
78
+ fi
79
+
80
+ LOG_ENTRY=$(jq -n -c \
81
+ --arg ts "$TIMESTAMP" \
82
+ --arg ev "$EVENT" \
83
+ --arg sid "$SESSION_SHORT" \
84
+ --arg tool "$TOOL_NAME" \
85
+ --arg status "$STATUS" \
86
+ --argjson summary "$TOOL_SUMMARY" \
87
+ '{timestamp: $ts, event: $ev, session: $sid, tool: $tool, status: $status, detail: $summary}')
88
+ ;;
89
+
90
+ Stop)
91
+ # transcript에서 마지막 assistant 턴의 토큰 사용량 추출
92
+ TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // ""')
93
+ USAGE='{}'
94
+
95
+ if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
96
+ # 마지막 assistant 메시지의 usage 추출
97
+ LAST_USAGE=$(tac "$TRANSCRIPT_PATH" | jq -c 'select(.type == "assistant" and .message.usage) | .message.usage' 2>/dev/null | head -1)
98
+
99
+ if [ -n "$LAST_USAGE" ] && [ "$LAST_USAGE" != "null" ]; then
100
+ USAGE=$(echo "$LAST_USAGE" | jq -c '{
101
+ input: (.input_tokens // 0),
102
+ output: (.output_tokens // 0),
103
+ cache_create: (.cache_creation_input_tokens // 0),
104
+ cache_read: (.cache_read_input_tokens // 0)
105
+ }')
106
+ fi
107
+
108
+ # 누적 토큰 합산
109
+ CUMULATIVE=$(jq -s -c '{
110
+ total_input: ([.[].message.usage.input_tokens // 0] | add),
111
+ total_output: ([.[].message.usage.output_tokens // 0] | add),
112
+ total_cache_create: ([.[].message.usage.cache_creation_input_tokens // 0] | add),
113
+ total_cache_read: ([.[].message.usage.cache_read_input_tokens // 0] | add),
114
+ turns: length
115
+ }' <(jq -c 'select(.type == "assistant" and .message.usage)' "$TRANSCRIPT_PATH" 2>/dev/null) 2>/dev/null || echo '{}')
116
+ fi
117
+
118
+ LOG_ENTRY=$(jq -n -c \
119
+ --arg ts "$TIMESTAMP" \
120
+ --arg ev "Stop" \
121
+ --arg sid "$SESSION_SHORT" \
122
+ --argjson usage "${USAGE:-'{}'}" \
123
+ --argjson cumulative "${CUMULATIVE:-'{}'}" \
124
+ '{timestamp: $ts, event: $ev, session: $sid, usage: $usage, cumulative: $cumulative}')
125
+ ;;
126
+
127
+ SessionStart)
128
+ SOURCE=$(echo "$INPUT" | jq -r '.source // "unknown"')
129
+ LOG_ENTRY=$(jq -n -c \
130
+ --arg ts "$TIMESTAMP" \
131
+ --arg ev "SessionStart" \
132
+ --arg sid "$SESSION_SHORT" \
133
+ --arg src "$SOURCE" \
134
+ --arg cwd "$(echo "$INPUT" | jq -r '.cwd // ""')" \
135
+ '{timestamp: $ts, event: $ev, session: $sid, source: $src, cwd: $cwd}')
136
+ ;;
137
+
138
+ SessionEnd)
139
+ # transcript에서 최종 토큰 합산
140
+ TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // ""')
141
+ TOTAL_USAGE='{}'
142
+
143
+ if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
144
+ TOTAL_USAGE=$(jq -s -c '{
145
+ total_input: ([.[].message.usage.input_tokens // 0] | add),
146
+ total_output: ([.[].message.usage.output_tokens // 0] | add),
147
+ total_cache_create: ([.[].message.usage.cache_creation_input_tokens // 0] | add),
148
+ total_cache_read: ([.[].message.usage.cache_read_input_tokens // 0] | add),
149
+ turns: length
150
+ }' <(jq -c 'select(.type == "assistant" and .message.usage)' "$TRANSCRIPT_PATH" 2>/dev/null) 2>/dev/null || echo '{}')
151
+ fi
152
+
153
+ LOG_ENTRY=$(jq -n -c \
154
+ --arg ts "$TIMESTAMP" \
155
+ --arg ev "SessionEnd" \
156
+ --arg sid "$SESSION_SHORT" \
157
+ --argjson total_usage "${TOTAL_USAGE:-'{}'}" \
158
+ '{timestamp: $ts, event: $ev, session: $sid, total_usage: $total_usage}')
159
+
160
+ # SessionEnd 시 자동화 스크립트 체인 실행
161
+ # 1. 작업 로그 자동 생성
162
+ AUTO_WORKLOG="$PROJECT_DIR/.claude/hooks/auto-worklog.sh"
163
+ if [ -f "$AUTO_WORKLOG" ]; then
164
+ bash "$AUTO_WORKLOG" "$LOG_FILE" "$SESSION_SHORT" &
165
+ fi
166
+
167
+ # 2. 메트릭 자동 수집 (누적 갱신, 토큰 비용 0)
168
+ AUTO_METRICS="$PROJECT_DIR/.claude/hooks/auto-metrics.sh"
169
+ if [ -f "$AUTO_METRICS" ]; then
170
+ bash "$AUTO_METRICS" "$LOG_FILE" "$SESSION_SHORT" &
171
+ fi
172
+ ;;
173
+
174
+ SubagentStart)
175
+ AGENT_TYPE=$(echo "$INPUT" | jq -r '.subagent_type // "unknown"')
176
+ AGENT_DESC=$(echo "$INPUT" | jq -r '.description // ""' | head -c 200)
177
+ LOG_ENTRY=$(jq -n -c \
178
+ --arg ts "$TIMESTAMP" \
179
+ --arg ev "SubagentStart" \
180
+ --arg sid "$SESSION_SHORT" \
181
+ --arg atype "$AGENT_TYPE" \
182
+ --arg desc "$AGENT_DESC" \
183
+ '{timestamp: $ts, event: $ev, session: $sid, detail: {subagent_type: $atype, description: $desc}}')
184
+ ;;
185
+
186
+ SubagentStop)
187
+ AGENT_TYPE=$(echo "$INPUT" | jq -r '.subagent_type // "unknown"')
188
+ LOG_ENTRY=$(jq -n -c \
189
+ --arg ts "$TIMESTAMP" \
190
+ --arg ev "SubagentStop" \
191
+ --arg sid "$SESSION_SHORT" \
192
+ --arg atype "$AGENT_TYPE" \
193
+ '{timestamp: $ts, event: $ev, session: $sid, detail: {subagent_type: $atype}}')
194
+ ;;
195
+
196
+ *)
197
+ LOG_ENTRY=$(jq -n -c \
198
+ --arg ts "$TIMESTAMP" \
199
+ --arg ev "$EVENT" \
200
+ --arg sid "$SESSION_SHORT" \
201
+ '{timestamp: $ts, event: $ev, session: $sid}')
202
+ ;;
203
+ esac
204
+
205
+ # JSONL 파일에 append
206
+ echo "$LOG_ENTRY" >> "$LOG_FILE"
207
+
208
+ exit 0