oh-my-customcode 1.1.54 → 1.1.56

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 (42) hide show
  1. package/dist/cli/index.js +1 -1
  2. package/dist/index.js +1 -1
  3. package/package.json +1 -1
  4. package/templates/.claude/hooks/hooks.json +19 -19
  5. package/templates/.claude/hooks/scripts/agent-start-recorder.sh +5 -5
  6. package/templates/.claude/hooks/scripts/agent-teams-advisor.sh +4 -4
  7. package/templates/.claude/hooks/scripts/audit-log.sh +6 -6
  8. package/templates/.claude/hooks/scripts/auto-continue-guard.sh +1 -1
  9. package/templates/.claude/hooks/scripts/auto-dev-token-summary.sh +6 -6
  10. package/templates/.claude/hooks/scripts/auto-dev-token-tracker.sh +6 -6
  11. package/templates/.claude/hooks/scripts/content-hash-validator.sh +4 -4
  12. package/templates/.claude/hooks/scripts/context-budget-advisor.sh +3 -3
  13. package/templates/.claude/hooks/scripts/cost-cap-advisor.sh +6 -6
  14. package/templates/.claude/hooks/scripts/cwd-change-detector.sh +3 -3
  15. package/templates/.claude/hooks/scripts/destructive-git-guard.sh +2 -2
  16. package/templates/.claude/hooks/scripts/eval-core-batch-save.sh +2 -2
  17. package/templates/.claude/hooks/scripts/feedback-collector.sh +7 -7
  18. package/templates/.claude/hooks/scripts/file-change-validator.sh +4 -4
  19. package/templates/.claude/hooks/scripts/git-delegation-guard.sh +3 -3
  20. package/templates/.claude/hooks/scripts/model-escalation-advisor.sh +4 -4
  21. package/templates/.claude/hooks/scripts/omcustom-auto-update.sh +6 -6
  22. package/templates/.claude/hooks/scripts/playwright-compress.sh +4 -4
  23. package/templates/.claude/hooks/scripts/plugin-cache-check.sh +2 -2
  24. package/templates/.claude/hooks/scripts/r007-r008-drift-advisor.sh +26 -3
  25. package/templates/.claude/hooks/scripts/rtk-intercept.sh +9 -9
  26. package/templates/.claude/hooks/scripts/rule-deletion-guard.sh +5 -5
  27. package/templates/.claude/hooks/scripts/schema-validator.sh +10 -10
  28. package/templates/.claude/hooks/scripts/secret-filter.sh +4 -4
  29. package/templates/.claude/hooks/scripts/session-autofix-prompt.sh +3 -3
  30. package/templates/.claude/hooks/scripts/session-autofix.sh +1 -1
  31. package/templates/.claude/hooks/scripts/session-env-check.sh +1 -1
  32. package/templates/.claude/hooks/scripts/session-reflection.sh +15 -15
  33. package/templates/.claude/hooks/scripts/skill-extractor-analyzer.sh +2 -2
  34. package/templates/.claude/hooks/scripts/stale-todo-scanner.sh +1 -1
  35. package/templates/.claude/hooks/scripts/stall-detection-advisor.sh +11 -11
  36. package/templates/.claude/hooks/scripts/stop-console-audit.sh +1 -1
  37. package/templates/.claude/hooks/scripts/stuck-detector.sh +81 -25
  38. package/templates/.claude/hooks/scripts/subagent-failure-advisor.sh +36 -0
  39. package/templates/.claude/hooks/scripts/task-outcome-recorder.sh +8 -8
  40. package/templates/.claude/hooks/scripts/task-state-precompact.sh +1 -1
  41. package/templates/.claude/settings.json +19 -19
  42. package/templates/manifest.json +1 -1
@@ -12,15 +12,15 @@ command -v jq >/dev/null 2>&1 || exit 0
12
12
  input=$(cat)
13
13
 
14
14
  # Extract current task info
15
- agent_type=$(echo "$input" | jq -r '.tool_input.subagent_type // "unknown"')
16
- current_model=$(echo "$input" | jq -r '.tool_input.model // "inherit"')
15
+ agent_type=$(printf '%s\n' "$input" | jq -r '.tool_input.subagent_type // "unknown"')
16
+ current_model=$(printf '%s\n' "$input" | jq -r '.tool_input.model // "inherit"')
17
17
 
18
18
  # Session-scoped outcome log
19
19
  OUTCOME_FILE="/tmp/.claude-task-outcomes-${PPID}"
20
20
 
21
21
  # Skip if no history
22
22
  if [ ! -f "$OUTCOME_FILE" ]; then
23
- echo "$input"
23
+ printf '%s\n' "$input"
24
24
  exit 0
25
25
  fi
26
26
 
@@ -107,5 +107,5 @@ if [ "$current_model" != "haiku" ] && [ "$current_model" != "inherit" ] && [ "$c
107
107
  fi
108
108
 
109
109
  # Pass through
110
- echo "$input"
110
+ printf '%s\n' "$input"
111
111
  exit 0
@@ -12,13 +12,13 @@ input=$(cat)
12
12
 
13
13
  # Skip if explicitly disabled
14
14
  if [ "${OMCUSTOM_SKIP_AUTO_UPDATE:-}" = "true" ]; then
15
- echo "$input"
15
+ printf '%s\n' "$input"
16
16
  exit 0
17
17
  fi
18
18
 
19
19
  # Skip if /dev/tty not available (CI, Docker, non-interactive)
20
20
  if ! [ -c /dev/tty ] 2>/dev/null; then
21
- echo "$input"
21
+ printf '%s\n' "$input"
22
22
  exit 0
23
23
  fi
24
24
 
@@ -53,7 +53,7 @@ fi
53
53
 
54
54
  # Cannot determine current version — skip
55
55
  if [ -z "$CURRENT_VERSION" ]; then
56
- echo "$input"
56
+ printf '%s\n' "$input"
57
57
  exit 0
58
58
  fi
59
59
 
@@ -101,14 +101,14 @@ fi
101
101
 
102
102
  # Could not determine latest version — skip
103
103
  if [ -z "$LATEST_VERSION" ]; then
104
- echo "$input"
104
+ printf '%s\n' "$input"
105
105
  exit 0
106
106
  fi
107
107
 
108
108
  # --- Step 3: Compare versions ---
109
109
  if ! version_lt "$CURRENT_VERSION" "$LATEST_VERSION"; then
110
110
  # Already up to date
111
- echo "$input"
111
+ printf '%s\n' "$input"
112
112
  exit 0
113
113
  fi
114
114
 
@@ -166,5 +166,5 @@ fi
166
166
  echo "------------------------------------" >&2
167
167
 
168
168
  # Always pass through and exit 0
169
- echo "$input"
169
+ printf '%s\n' "$input"
170
170
  exit 0
@@ -7,12 +7,12 @@
7
7
  set -euo pipefail
8
8
 
9
9
  input=$(cat)
10
- tool_output=$(echo "$input" | jq -r '.tool_output // ""')
10
+ tool_output=$(printf '%s\n' "$input" | jq -r '.tool_output // ""')
11
11
 
12
12
  # Skip if output is small (< 3000 chars)
13
13
  output_len=${#tool_output}
14
14
  if [ "$output_len" -lt 3000 ]; then
15
- echo "$input"
15
+ printf '%s\n' "$input"
16
16
  exit 0
17
17
  fi
18
18
 
@@ -22,7 +22,7 @@ refs=$(echo "$tool_output" | grep -oE 'ref="[^"]*"' | sort -u || true)
22
22
  # Summarize using Haiku via subscription auth
23
23
  summary=$(echo "$tool_output" | claude -p --model haiku "Summarize this browser page content concisely. Preserve ALL ref= attribute values exactly as they appear. Focus on: page structure, interactive elements with their ref values, visible text content, and any error messages." 2>/dev/null) || {
24
24
  # Fallback: return original on failure
25
- echo "$input"
25
+ printf '%s\n' "$input"
26
26
  exit 0
27
27
  }
28
28
 
@@ -46,5 +46,5 @@ fi
46
46
  # Return compressed output
47
47
  compressed_len=${#summary}
48
48
  savings=$(( (output_len - compressed_len) * 100 / output_len ))
49
- echo "$input" | jq --arg summary "$summary" --arg savings "${savings}% reduced (${output_len}→${compressed_len} chars)" \
49
+ printf '%s\n' "$input" | jq --arg summary "$summary" --arg savings "${savings}% reduced (${output_len}→${compressed_len} chars)" \
50
50
  '.tool_output = $summary | .["updatedMCPToolOutput"] = $summary'
@@ -12,7 +12,7 @@ input=$(cat)
12
12
  PLUGIN_CACHE="${HOME}/.claude/shared-plugins/cache"
13
13
 
14
14
  if [ ! -d "$PLUGIN_CACHE" ]; then
15
- echo "$input"
15
+ printf '%s\n' "$input"
16
16
  exit 0
17
17
  fi
18
18
 
@@ -31,5 +31,5 @@ if [ ${#missing[@]} -gt 0 ]; then
31
31
  done
32
32
  fi
33
33
 
34
- echo "$input"
34
+ printf '%s\n' "$input"
35
35
  exit 0
@@ -192,13 +192,26 @@ split("\n")
192
192
  # 올바르게 분리되어 0/0이었음). 이 조사에서 확정된 실제 원인은 별도 메커니즘이다:
193
193
  # tail -n 200 윈도우 안에 경계가 전혀 없을 때 $b=-1로 폴백해 슬라이딩 윈도우 전체를
194
194
  # 하나의 turn으로 병합하는 구조 — 본 수정의 승인 범위(0-tool-use 가드 1건) 밖이므로
195
- # 별도 이슈로 보고한다(오케스트레이터 완료 보고 참조).
195
+ # 별도 이슈로 보고한다(오케스트레이터 완료 보고 참조). → #1628로 등록·수정 완료:
196
+ # 아래 $b 바인딩 직후의 "(#1628)" 주석 및 $wlc 포화 가드 참조.
196
197
  | [ range(0; $last + 1)
197
198
  | select( ($L[.].message.role? == "user")
198
199
  and ( (($L[.].message.content | type) == "string")
199
200
  or (([ $L[.].message.content[]? | select(.type? == "tool_result") ] | length) == 0) ) ) ] as $bi
200
201
  | (if ($bi | length) > 0 then $bi[-1] else -1 end) as $b
201
- | [ range($b + 1; $last + 1) | $L[.] | select(.message.role? == "assistant") ] as $turn
202
+ # (#1628) 경계가 전혀 없는 경우($bi 비어있음)를 두 케이스로 나눈다:
203
+ # (a) 윈도우가 포화($wlc >= 200 — tail -n 200이 실제로 200줄을 반환) — 윈도우 밖에
204
+ # 진짜 경계가 있었을 수 있다. 이 경우 $b=-1로 윈도우 전체를 하나의 turn으로
205
+ # 병합하면, 서로 독립적이고 각각 컴플라이언트한 여러 응답이 합쳐져 announce/
206
+ # tool_use 카운트가 어긋나 R008 위양성이 5~10건대로 요동친다(#1628 실측). 판정
207
+ # 자체를 스킵한다(empty — 아래에서 result가 비어 exit 0으로 이어진다).
208
+ # (b) 윈도우가 포화되지 않음($wlc < 200 — tail이 transcript 전체를 다 읽었다는 뜻) —
209
+ # 세션이 정말로 짧아 첫 턴부터 지금까지 경계가 없는 것이므로, 기존 $b=-1 폴백을
210
+ # 그대로 유지한다(#1625 찐빠 #3이 확정한 자율 루프 경계 대체 신호와 동일 로직).
211
+ | if ($bi | length) == 0 and $wlc >= 200 then empty
212
+ else
213
+ (
214
+ [ range($b + 1; $last + 1) | $L[.] | select(.message.role? == "assistant") ] as $turn
202
215
  | [ $turn[] | .message.content[]? | select(.type? != "thinking") ] as $blocks
203
216
  | ($turn[0].uuid? // "") as $tuuid
204
217
  | ([ $blocks[] | select(.type? == "text") ][0].text? // "") as $ftext
@@ -237,10 +250,20 @@ split("\n")
237
250
  # $nall_tools는 위 forward r008 가드에서 이미 바인딩됨 — 재바인딩하지 않고 재사용한다.
238
251
  | (if $nall_tools == 0 and $an_anchored > 0 then $an_anchored else 0 end) as $r008rev
239
252
  | [$tuuid, ($r007 | tostring), ($r008 | tostring), ($r008rev | tostring)] | @tsv
253
+ )
254
+ end
240
255
  end
241
256
  '
242
257
 
243
- result=$(tail -n 200 "$TRANSCRIPT_PATH" 2>/dev/null | jq -Rsr "$JQ_LAST_TURN" 2>/dev/null) || result=""
258
+ # ── 윈도우 포화 여부 판별 (#1628) ──
259
+ # tail -n 200이 실제로 200줄을 반환했는지(포화 — 윈도우 밖에 잘려나간 내용이 있을 수 있음)를
260
+ # jq 호출 전에 bash에서 측정해 $wlc로 전달한다. awk는 트레일링 개행 유무와 무관하게 정확한
261
+ # 레코드 수를 센다(wc -l은 트레일링 개행이 없는 마지막 줄을 놓칠 수 있음).
262
+ window_content=$(tail -n 200 "$TRANSCRIPT_PATH" 2>/dev/null) || window_content=""
263
+ window_line_count=$(printf '%s' "$window_content" | awk 'END{print NR}')
264
+ : "${window_line_count:=0}"
265
+
266
+ result=$(printf '%s' "$window_content" | jq -Rsr --argjson wlc "$window_line_count" "$JQ_LAST_TURN" 2>/dev/null) || result=""
244
267
 
245
268
  if [ -z "$result" ]; then
246
269
  exit 0
@@ -9,9 +9,9 @@ set -euo pipefail
9
9
  input=$(cat)
10
10
 
11
11
  # Only intercept Bash tool calls
12
- tool_name=$(echo "$input" | jq -r '.tool // empty' 2>/dev/null || echo "")
12
+ tool_name=$(printf '%s\n' "$input" | jq -r '.tool // empty' 2>/dev/null || echo "")
13
13
  if [ "$tool_name" != "Bash" ]; then
14
- echo "$input"
14
+ printf '%s\n' "$input"
15
15
  exit 0
16
16
  fi
17
17
 
@@ -25,26 +25,26 @@ elif command -v rtk >/dev/null 2>&1; then
25
25
  fi
26
26
 
27
27
  if [ "$RTK_AVAILABLE" != "true" ]; then
28
- echo "$input"
28
+ printf '%s\n' "$input"
29
29
  exit 0
30
30
  fi
31
31
 
32
32
  # Extract command
33
- cmd=$(echo "$input" | jq -r '.tool_input.command // empty' 2>/dev/null || echo "")
33
+ cmd=$(printf '%s\n' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null || echo "")
34
34
  if [ -z "$cmd" ]; then
35
- echo "$input"
35
+ printf '%s\n' "$input"
36
36
  exit 0
37
37
  fi
38
38
 
39
39
  # Skip if already using rtk
40
40
  if echo "$cmd" | grep -qE '^rtk\b'; then
41
- echo "$input"
41
+ printf '%s\n' "$input"
42
42
  exit 0
43
43
  fi
44
44
 
45
45
  # Skip complex commands (pipes, redirections, background, subshells, semicolons with multiple commands)
46
46
  if echo "$cmd" | grep -qE '[|><&]|;\s*\w'; then
47
- echo "$input"
47
+ printf '%s\n' "$input"
48
48
  exit 0
49
49
  fi
50
50
 
@@ -64,7 +64,7 @@ for rtk_cmd in $RTK_CMDS; do
64
64
  done
65
65
 
66
66
  if [ "$SUPPORTED" != "true" ]; then
67
- echo "$input"
67
+ printf '%s\n' "$input"
68
68
  exit 0
69
69
  fi
70
70
 
@@ -73,5 +73,5 @@ new_cmd="rtk $cmd"
73
73
  echo "[RTK] Intercepted: $cmd → $new_cmd" >&2
74
74
 
75
75
  # Output modified JSON
76
- echo "$input" | jq --arg new_cmd "$new_cmd" '.tool_input.command = $new_cmd'
76
+ printf '%s\n' "$input" | jq --arg new_cmd "$new_cmd" '.tool_input.command = $new_cmd'
77
77
  exit 0
@@ -8,17 +8,17 @@ input=$(cat)
8
8
 
9
9
  # Dependency check — allow if jq missing
10
10
  if ! command -v jq &>/dev/null; then
11
- echo "$input"
11
+ printf '%s\n' "$input"
12
12
  exit 0
13
13
  fi
14
14
 
15
15
  # Parse tool input
16
- tool=$(echo "$input" | jq -r '.tool // ""' 2>/dev/null) || { echo "$input"; exit 0; }
17
- cmd=$(echo "$input" | jq -r '.tool_input.command // ""' 2>/dev/null) || { echo "$input"; exit 0; }
16
+ tool=$(printf '%s\n' "$input" | jq -r '.tool // ""' 2>/dev/null) || { printf '%s\n' "$input"; exit 0; }
17
+ cmd=$(printf '%s\n' "$input" | jq -r '.tool_input.command // ""' 2>/dev/null) || { printf '%s\n' "$input"; exit 0; }
18
18
 
19
19
  # Only check Bash tool
20
20
  if [ "$tool" != "Bash" ]; then
21
- echo "$input"
21
+ printf '%s\n' "$input"
22
22
  exit 0
23
23
  fi
24
24
 
@@ -56,5 +56,5 @@ if echo "$cmd" | grep -qE '(^|\s)(rm|git\s+rm|mv|unlink)\s' && echo "$cmd" | gre
56
56
  fi
57
57
 
58
58
  # Not a rule deletion — pass through
59
- echo "$input"
59
+ printf '%s\n' "$input"
60
60
  exit 0
@@ -13,14 +13,14 @@ command -v jq >/dev/null 2>&1 || exit 0
13
13
  input=$(cat)
14
14
 
15
15
  # Extract tool info
16
- tool_name=$(echo "$input" | jq -r '.tool_name // "unknown"')
17
- tool_input=$(echo "$input" | jq -r '.tool_input // {}')
16
+ tool_name=$(printf '%s\n' "$input" | jq -r '.tool_name // "unknown"')
17
+ tool_input=$(printf '%s\n' "$input" | jq -r '.tool_input // {}')
18
18
 
19
19
  SCHEMA_FILE=".claude/schemas/tool-inputs.json"
20
20
 
21
21
  # Skip if schema file doesn't exist
22
22
  if [ ! -f "$SCHEMA_FILE" ]; then
23
- echo "$input"
23
+ printf '%s\n' "$input"
24
24
  exit 0
25
25
  fi
26
26
 
@@ -28,8 +28,8 @@ warnings=()
28
28
 
29
29
  case "$tool_name" in
30
30
  "Write")
31
- file_path=$(echo "$tool_input" | jq -r '.file_path // ""')
32
- content=$(echo "$tool_input" | jq -r '.content // ""')
31
+ file_path=$(printf '%s\n' "$tool_input" | jq -r '.file_path // ""')
32
+ content=$(printf '%s\n' "$tool_input" | jq -r '.content // ""')
33
33
 
34
34
  if [ -z "$file_path" ]; then
35
35
  warnings+=("[Schema] Write: file_path is empty or missing")
@@ -40,9 +40,9 @@ case "$tool_name" in
40
40
  ;;
41
41
 
42
42
  "Edit")
43
- file_path=$(echo "$tool_input" | jq -r '.file_path // ""')
44
- old_string=$(echo "$tool_input" | jq -r '.old_string // ""')
45
- new_string=$(echo "$tool_input" | jq -r '.new_string // ""')
43
+ file_path=$(printf '%s\n' "$tool_input" | jq -r '.file_path // ""')
44
+ old_string=$(printf '%s\n' "$tool_input" | jq -r '.old_string // ""')
45
+ new_string=$(printf '%s\n' "$tool_input" | jq -r '.new_string // ""')
46
46
 
47
47
  if [ -z "$file_path" ]; then
48
48
  warnings+=("[Schema] Edit: file_path is empty or missing")
@@ -56,7 +56,7 @@ case "$tool_name" in
56
56
  ;;
57
57
 
58
58
  "Bash")
59
- command=$(echo "$tool_input" | jq -r '.command // ""')
59
+ command=$(printf '%s\n' "$tool_input" | jq -r '.command // ""')
60
60
 
61
61
  if [ -z "$command" ]; then
62
62
  warnings+=("[Schema] Bash: command is empty")
@@ -102,5 +102,5 @@ if [ ${#warnings[@]} -gt 0 ]; then
102
102
  fi
103
103
 
104
104
  # Always pass through (Phase 1)
105
- echo "$input"
105
+ printf '%s\n' "$input"
106
106
  exit 0
@@ -13,12 +13,12 @@ command -v jq >/dev/null 2>&1 || exit 0
13
13
  input=$(cat)
14
14
 
15
15
  # Extract output to scan
16
- tool_name=$(echo "$input" | jq -r '.tool_name // "unknown"')
17
- output=$(echo "$input" | jq -r '.tool_output.output // ""')
16
+ tool_name=$(printf '%s\n' "$input" | jq -r '.tool_name // "unknown"')
17
+ output=$(printf '%s\n' "$input" | jq -r '.tool_output.output // ""')
18
18
 
19
19
  # Skip if no output
20
20
  if [ -z "$output" ] || [ "$output" = "null" ]; then
21
- echo "$input"
21
+ printf '%s\n' "$input"
22
22
  exit 0
23
23
  fi
24
24
 
@@ -96,5 +96,5 @@ if [ "$detected" = true ]; then
96
96
  fi
97
97
 
98
98
  # Pass through (always)
99
- echo "$input"
99
+ printf '%s\n' "$input"
100
100
  exit 0
@@ -14,16 +14,16 @@ fi
14
14
  FINDINGS=$(cat "$FIXES_FILE")
15
15
  rm -f "$FIXES_FILE"
16
16
 
17
- ISSUE_COUNT=$(echo "$FINDINGS" | jq -r '.issue_count // 0' 2>/dev/null)
17
+ ISSUE_COUNT=$(printf '%s\n' "$FINDINGS" | jq -r '.issue_count // 0' 2>/dev/null)
18
18
 
19
19
  if [ "$ISSUE_COUNT" -gt 0 ]; then
20
20
  echo "[Session Auto-Fix] Previous session left ${ISSUE_COUNT} issue(s):"
21
- echo "$FINDINGS" | jq -r '.issues[]' 2>/dev/null | while IFS= read -r issue; do
21
+ printf '%s\n' "$FINDINGS" | jq -r '.issues[]' 2>/dev/null | while IFS= read -r issue; do
22
22
  type="${issue%%:*}"
23
23
  msg="${issue#*:}"
24
24
  echo " - [${type}] ${msg}"
25
25
  done
26
- FIX_COUNT=$(echo "$FINDINGS" | jq -r '.fix_count // 0' 2>/dev/null)
26
+ FIX_COUNT=$(printf '%s\n' "$FINDINGS" | jq -r '.fix_count // 0' 2>/dev/null)
27
27
  if [ "$FIX_COUNT" -gt 0 ]; then
28
28
  echo "Auto-fixed: ${FIX_COUNT} item(s)."
29
29
  fi
@@ -142,5 +142,5 @@ fi
142
142
  mkdir -p "$LOG_DIR" 2>/dev/null
143
143
  echo "{\"date\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"issue_count\":${ISSUE_COUNT},\"fix_count\":${FIX_COUNT}}" >> "${LOG_DIR}/$(date +%Y-%m-%d).jsonl" 2>/dev/null
144
144
 
145
- echo "$input"
145
+ printf '%s\n' "$input"
146
146
  exit 0
@@ -298,5 +298,5 @@ fi
298
298
  echo "------------------------------------" >&2
299
299
 
300
300
  # Pass through
301
- echo "$input"
301
+ printf '%s\n' "$input"
302
302
  exit 0
@@ -15,32 +15,32 @@ input=$(cat)
15
15
 
16
16
  # ── Opt-out 체크 ──
17
17
  if [ "${OMCUSTOM_SESSION_REFLECTION:-}" = "off" ]; then
18
- echo "$input"
18
+ printf '%s\n' "$input"
19
19
  exit 0
20
20
  fi
21
21
 
22
22
  # ── jq 의존성 체크 ──
23
23
  if ! command -v jq >/dev/null 2>&1; then
24
- echo "$input"
24
+ printf '%s\n' "$input"
25
25
  exit 0
26
26
  fi
27
27
 
28
28
  # ── session_id 추출 ──
29
- session_id=$(echo "$input" | jq -r '.session_id // empty' 2>/dev/null)
29
+ session_id=$(printf '%s\n' "$input" | jq -r '.session_id // empty' 2>/dev/null)
30
30
  if [ -z "$session_id" ]; then
31
- echo "$input"
31
+ printf '%s\n' "$input"
32
32
  exit 0
33
33
  fi
34
34
 
35
35
  # ── Phase 2 (#1196): background_tasks / session_crons 추출 (CC v2.1.145+) ──
36
36
  # 신규 필드 미존재 시 빈 배열로 처리 (graceful degrade).
37
- bg_tasks_json=$(echo "$input" | jq -c '.background_tasks // []' 2>/dev/null || echo '[]')
38
- session_crons_json=$(echo "$input" | jq -c '.session_crons // []' 2>/dev/null || echo '[]')
39
- bg_tasks_count=$(echo "$bg_tasks_json" | jq 'length' 2>/dev/null || echo 0)
40
- session_crons_count=$(echo "$session_crons_json" | jq 'length' 2>/dev/null || echo 0)
37
+ bg_tasks_json=$(printf '%s\n' "$input" | jq -c '.background_tasks // []' 2>/dev/null || echo '[]')
38
+ session_crons_json=$(printf '%s\n' "$input" | jq -c '.session_crons // []' 2>/dev/null || echo '[]')
39
+ bg_tasks_count=$(printf '%s\n' "$bg_tasks_json" | jq 'length' 2>/dev/null || echo 0)
40
+ session_crons_count=$(printf '%s\n' "$session_crons_json" | jq 'length' 2>/dev/null || echo 0)
41
41
 
42
42
  # 미완료 백그라운드 태스크: running/pending/in_progress 상태 카운트
43
- bg_dangling_count=$(echo "$bg_tasks_json" | jq '[.[] | select(.status == "running" or .status == "pending" or .status == "in_progress")] | length' 2>/dev/null || echo 0)
43
+ bg_dangling_count=$(printf '%s\n' "$bg_tasks_json" | jq '[.[] | select(.status == "running" or .status == "pending" or .status == "in_progress")] | length' 2>/dev/null || echo 0)
44
44
 
45
45
  # ── 경로 결정 (환경변수 override 지원) ──
46
46
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -50,7 +50,7 @@ TRANSCRIPT_BASE="${OMCUSTOM_TRANSCRIPT_BASE:-${HOME}/.claude/projects/-Users-san
50
50
  TRANSCRIPT_PATH="${TRANSCRIPT_BASE}/${session_id}.jsonl"
51
51
 
52
52
  if [ ! -f "$TRANSCRIPT_PATH" ]; then
53
- echo "$input"
53
+ printf '%s\n' "$input"
54
54
  exit 0
55
55
  fi
56
56
 
@@ -179,14 +179,14 @@ else
179
179
  fi
180
180
 
181
181
  # ── Phase 2 (#1196): background_tasks / session_crons 분석 ──
182
- bg_total=$(echo "$BG_TASKS_JSON" | jq 'length' 2>/dev/null || echo 0)
183
- cron_total=$(echo "$SESSION_CRONS_JSON" | jq 'length' 2>/dev/null || echo 0)
182
+ bg_total=$(printf '%s\n' "$BG_TASKS_JSON" | jq 'length' 2>/dev/null || echo 0)
183
+ cron_total=$(printf '%s\n' "$SESSION_CRONS_JSON" | jq 'length' 2>/dev/null || echo 0)
184
184
  bg_dangling_lines=""
185
185
  cron_lines=""
186
186
 
187
187
  if [ "$bg_total" -gt 0 ]; then
188
188
  # dangling 태스크 목록 (최대 5개): id + status + description (60자 truncate)
189
- bg_dangling_lines=$(echo "$BG_TASKS_JSON" | jq -r '
189
+ bg_dangling_lines=$(printf '%s\n' "$BG_TASKS_JSON" | jq -r '
190
190
  [.[] | select(.status == "running" or .status == "pending" or .status == "in_progress")]
191
191
  | .[0:5]
192
192
  | map(" - id=\(.id // "?") status=\(.status // "?") desc=\((.description // "") | .[0:60])")
@@ -195,7 +195,7 @@ if [ "$bg_total" -gt 0 ]; then
195
195
  fi
196
196
 
197
197
  if [ "$cron_total" -gt 0 ]; then
198
- cron_lines=$(echo "$SESSION_CRONS_JSON" | jq -r '
198
+ cron_lines=$(printf '%s\n' "$SESSION_CRONS_JSON" | jq -r '
199
199
  .[0:5]
200
200
  | map(" - id=\(.id // "?") schedule=\(.schedule // "?") prompt=\((.prompt // "") | .[0:60])")
201
201
  | join("\n")
@@ -253,5 +253,5 @@ WORKER_ERR_LOG="/tmp/.claude-reflection-err-${PPID}.log"
253
253
  disown $! 2>/dev/null || true
254
254
 
255
255
  # ── 즉시 stdin pass-through 후 exit 0 (Stop hook 체인 유지) ──
256
- echo "$input"
256
+ printf '%s\n' "$input"
257
257
  exit 0
@@ -12,7 +12,7 @@ PROPOSALS_FILE="/tmp/.claude-skill-proposals-${PPID}"
12
12
 
13
13
  # Early exit if no outcomes
14
14
  if [ ! -f "$OUTCOMES_FILE" ] || [ ! -s "$OUTCOMES_FILE" ]; then
15
- echo "$input"
15
+ printf '%s\n' "$input"
16
16
  exit 0
17
17
  fi
18
18
 
@@ -45,5 +45,5 @@ fi
45
45
 
46
46
  # CRITICAL: Always pass through input and exit 0
47
47
  # This hook MUST NEVER block session termination
48
- echo "$input"
48
+ printf '%s\n' "$input"
49
49
  exit 0
@@ -78,5 +78,5 @@ fi
78
78
  echo "------------------------------------" >&2
79
79
 
80
80
  # Pass through
81
- echo "$input"
81
+ printf '%s\n' "$input"
82
82
  exit 0
@@ -17,18 +17,18 @@ AGENT_START_FILE="/tmp/.claude-agent-starts-${PPID}"
17
17
  DURATION_FILE="/tmp/.claude-agent-durations-${PPID}"
18
18
 
19
19
  # Skip if no start records exist
20
- [ -f "$AGENT_START_FILE" ] || { echo "$input"; exit 0; }
20
+ [ -f "$AGENT_START_FILE" ] || { printf '%s\n' "$input"; exit 0; }
21
21
 
22
22
  # --- 1. Extract completed agent info ---
23
- agent_type=$(echo "$input" | jq -r '.agent_type // "unknown"')
24
- model=$(echo "$input" | jq -r '.model // "inherit"')
25
- description=$(echo "$input" | jq -r '.description // ""' | head -c 80)
23
+ agent_type=$(printf '%s\n' "$input" | jq -r '.agent_type // "unknown"')
24
+ model=$(printf '%s\n' "$input" | jq -r '.model // "inherit"')
25
+ description=$(printf '%s\n' "$input" | jq -r '.description // ""' | head -c 80)
26
26
 
27
27
  # --- 2. Calculate duration from start record ---
28
28
  start_epoch=$(grep -F "\"agent_type\":\"${agent_type}\"" "$AGENT_START_FILE" 2>/dev/null | tail -1 | jq -r '.start_epoch // "0"' 2>/dev/null || echo "0")
29
29
 
30
30
  if [ "$start_epoch" = "0" ] || [ "$start_epoch" = "null" ]; then
31
- echo "$input"
31
+ printf '%s\n' "$input"
32
32
  exit 0
33
33
  fi
34
34
 
@@ -56,10 +56,10 @@ if [ "$completed_count" -ge 1 ]; then
56
56
  # Check for still-running agents (in start file but not in duration file)
57
57
  if [ -f "$AGENT_START_FILE" ] && [ -s "$AGENT_START_FILE" ]; then
58
58
  while IFS= read -r line; do
59
- running_agent=$(echo "$line" | jq -r '.agent_type // ""' 2>/dev/null || true)
60
- running_start=$(echo "$line" | jq -r '.start_epoch // "0"' 2>/dev/null || echo "0")
61
- running_desc=$(echo "$line" | jq -r '.description // ""' 2>/dev/null || true)
62
- running_model=$(echo "$line" | jq -r '.model // "inherit"' 2>/dev/null || true)
59
+ running_agent=$(printf '%s\n' "$line" | jq -r '.agent_type // ""' 2>/dev/null || true)
60
+ running_start=$(printf '%s\n' "$line" | jq -r '.start_epoch // "0"' 2>/dev/null || echo "0")
61
+ running_desc=$(printf '%s\n' "$line" | jq -r '.description // ""' 2>/dev/null || true)
62
+ running_model=$(printf '%s\n' "$line" | jq -r '.model // "inherit"' 2>/dev/null || true)
63
63
 
64
64
  if [ "$running_start" = "0" ] || [ "$running_start" = "null" ]; then continue; fi
65
65
 
@@ -90,7 +90,7 @@ duration_entry=$(jq -cn \
90
90
  --arg ts "$now_epoch" \
91
91
  '{agent_type: $agent, model: $model, description: $desc, duration_seconds: ($dur | tonumber), timestamp: $ts}')
92
92
 
93
- echo "$duration_entry" >> "$DURATION_FILE"
93
+ printf '%s\n' "$duration_entry" >> "$DURATION_FILE"
94
94
 
95
95
  # Remove only first consumed start entry (preserve siblings for parallel same-type agents)
96
96
  if [ -f "$AGENT_START_FILE" ]; then
@@ -108,5 +108,5 @@ if [ -f "$DURATION_FILE" ]; then
108
108
  fi
109
109
 
110
110
  # Pass through
111
- echo "$input"
111
+ printf '%s\n' "$input"
112
112
  exit 0
@@ -42,5 +42,5 @@ echo "[Stop] Audit complete. Session safe to terminate." >&2
42
42
 
43
43
  # CRITICAL: Always pass through input and exit 0
44
44
  # This hook MUST NEVER block session termination
45
- echo "$input"
45
+ printf '%s\n' "$input"
46
46
  exit 0