oh-my-customcode 1.1.55 → 1.1.57

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 +5 -1
  2. package/dist/index.js +5 -1
  3. package/package.json +1 -1
  4. package/templates/.claude/hooks/hooks.json +21 -21
  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/claude-md-reinject.sh +9 -4
  12. package/templates/.claude/hooks/scripts/content-hash-validator.sh +4 -4
  13. package/templates/.claude/hooks/scripts/context-budget-advisor.sh +3 -3
  14. package/templates/.claude/hooks/scripts/cost-cap-advisor.sh +6 -6
  15. package/templates/.claude/hooks/scripts/cwd-change-detector.sh +3 -3
  16. package/templates/.claude/hooks/scripts/destructive-git-guard.sh +2 -2
  17. package/templates/.claude/hooks/scripts/eval-core-batch-save.sh +2 -2
  18. package/templates/.claude/hooks/scripts/feedback-collector.sh +7 -7
  19. package/templates/.claude/hooks/scripts/file-change-validator.sh +4 -4
  20. package/templates/.claude/hooks/scripts/git-delegation-guard.sh +3 -3
  21. package/templates/.claude/hooks/scripts/model-escalation-advisor.sh +4 -4
  22. package/templates/.claude/hooks/scripts/omcustom-auto-update.sh +6 -6
  23. package/templates/.claude/hooks/scripts/playwright-compress.sh +4 -4
  24. package/templates/.claude/hooks/scripts/plugin-cache-check.sh +2 -2
  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 +5 -5
  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 +21 -21
  42. package/templates/manifest.json +1 -1
@@ -8,16 +8,16 @@ set -euo pipefail
8
8
  input=$(cat)
9
9
 
10
10
  # Dependencies check
11
- command -v jq >/dev/null 2>&1 || { echo "$input"; exit 0; }
12
- command -v sqlite3 >/dev/null 2>&1 || { echo "$input"; exit 0; }
11
+ command -v jq >/dev/null 2>&1 || { printf '%s\n' "$input"; exit 0; }
12
+ command -v sqlite3 >/dev/null 2>&1 || { printf '%s\n' "$input"; exit 0; }
13
13
 
14
14
  # PID scoping
15
15
  OUTCOMES_FILE="/tmp/.claude-task-outcomes-${PPID}"
16
- [ -f "$OUTCOMES_FILE" ] || { echo "$input"; exit 0; }
16
+ [ -f "$OUTCOMES_FILE" ] || { printf '%s\n' "$input"; exit 0; }
17
17
 
18
18
  # DB path
19
19
  DB_PATH="${HOME}/.config/oh-my-customcode/eval-core.sqlite"
20
- [ -f "$DB_PATH" ] || { echo "$input"; exit 0; }
20
+ [ -f "$DB_PATH" ] || { printf '%s\n' "$input"; exit 0; }
21
21
 
22
22
  # Log file for error diagnostics
23
23
  LOG_FILE="/tmp/.claude-feedback-collector-${PPID}.log"
@@ -30,8 +30,8 @@ declare -A FAILURE_COUNTS
30
30
  declare -A TOTAL_COUNTS
31
31
 
32
32
  while IFS= read -r line; do
33
- agent_type=$(echo "$line" | jq -r '.agent_type // empty' 2>/dev/null) || continue
34
- outcome=$(echo "$line" | jq -r '.outcome // empty' 2>/dev/null) || continue
33
+ agent_type=$(printf '%s\n' "$line" | jq -r '.agent_type // empty' 2>/dev/null) || continue
34
+ outcome=$(printf '%s\n' "$line" | jq -r '.outcome // empty' 2>/dev/null) || continue
35
35
  [ -z "$agent_type" ] && continue
36
36
 
37
37
  TOTAL_COUNTS[$agent_type]=$(( ${TOTAL_COUNTS[$agent_type]:-0} + 1 ))
@@ -88,5 +88,5 @@ fi
88
88
 
89
89
  # CRITICAL: Always pass through input and exit 0
90
90
  # This hook MUST NEVER block session termination
91
- echo "$input"
91
+ printf '%s\n' "$input"
92
92
  exit 0
@@ -3,11 +3,11 @@
3
3
  # Advisory warning when files are modified outside Claude Code
4
4
 
5
5
  input=$(cat)
6
- file_path=$(echo "$input" | jq -r '.file_path // ""' 2>/dev/null)
7
- change_type=$(echo "$input" | jq -r '.change_type // "modified"' 2>/dev/null)
6
+ file_path=$(printf '%s\n' "$input" | jq -r '.file_path // ""' 2>/dev/null)
7
+ change_type=$(printf '%s\n' "$input" | jq -r '.change_type // "modified"' 2>/dev/null)
8
8
 
9
9
  if [ -z "$file_path" ]; then
10
- echo "$input"
10
+ printf '%s\n' "$input"
11
11
  exit 0
12
12
  fi
13
13
 
@@ -23,4 +23,4 @@ case "$file_path" in
23
23
  ;;
24
24
  esac
25
25
 
26
- echo "$input"
26
+ printf '%s\n' "$input"
@@ -14,8 +14,8 @@ command -v jq >/dev/null 2>&1 || exit 0
14
14
 
15
15
  input=$(cat)
16
16
 
17
- agent_type=$(echo "$input" | jq -r '.tool_input.subagent_type // ""')
18
- prompt=$(echo "$input" | jq -r '.tool_input.prompt // ""')
17
+ agent_type=$(printf '%s\n' "$input" | jq -r '.tool_input.subagent_type // ""')
18
+ prompt=$(printf '%s\n' "$input" | jq -r '.tool_input.prompt // ""')
19
19
 
20
20
  # R010 violation tracking file (PPID-scoped for session persistence)
21
21
  VIOLATION_FILE="/tmp/.claude-r010-violations-${PPID}"
@@ -54,4 +54,4 @@ if [ "$agent_type" != "mgr-gitnerd" ]; then
54
54
  fi
55
55
 
56
56
  # Always pass through - this hook is advisory only
57
- echo "$input"
57
+ printf '%s\n' "$input"
@@ -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
@@ -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
@@ -203,7 +203,7 @@ entry=$(jq -cn \
203
203
  --arg readonly "$is_readonly" \
204
204
  '{timestamp: $ts, tool: $tool, path: $path, is_error: $err, error_hash: $hash, preview: $preview, readonly: $readonly}')
205
205
 
206
- echo "$entry" >> "$HISTORY_FILE"
206
+ printf '%s\n' "$entry" >> "$HISTORY_FILE"
207
207
 
208
208
  # Ring buffer: keep last 100 entries
209
209
  if [ -f "$HISTORY_FILE" ]; then
@@ -218,13 +218,13 @@ fi
218
218
 
219
219
  # Only check for patterns if we have enough history
220
220
  if [ ! -f "$HISTORY_FILE" ]; then
221
- echo "$input"
221
+ printf '%s\n' "$input"
222
222
  exit 0
223
223
  fi
224
224
 
225
225
  recent_count=$(wc -l < "$HISTORY_FILE")
226
226
  if [ "$recent_count" -lt 3 ]; then
227
- echo "$input"
227
+ printf '%s\n' "$input"
228
228
  exit 0
229
229
  fi
230
230
 
@@ -347,7 +347,7 @@ if [ "$hard_block" = true ]; then
347
347
  echo " Action: Blocking this tool call to break the stuck loop." >&2
348
348
  echo " Recovery: Step back, re-read the error, and try a fundamentally different approach." >&2
349
349
  echo "=====================================" >&2
350
- echo "$input"
350
+ printf '%s\n' "$input"
351
351
  HOOK_END=$(date +%s%N 2>/dev/null || echo 0)
352
352
  if [ "$HOOK_START" != "0" ] && [ "$HOOK_END" != "0" ]; then
353
353
  HOOK_MS=$(( (HOOK_END - HOOK_START) / 1000000 ))
@@ -357,7 +357,7 @@ if [ "$hard_block" = true ]; then
357
357
  fi
358
358
 
359
359
  # Pass through
360
- echo "$input"
360
+ printf '%s\n' "$input"
361
361
  HOOK_END=$(date +%s%N 2>/dev/null || echo 0)
362
362
  if [ "$HOOK_START" != "0" ] && [ "$HOOK_END" != "0" ]; then
363
363
  HOOK_MS=$(( (HOOK_END - HOOK_START) / 1000000 ))