claude_hooks 0.2.1 → 1.0.2

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +137 -0
  3. data/README.md +287 -356
  4. data/claude_hooks.gemspec +2 -2
  5. data/docs/1.0.0_MIGRATION_GUIDE.md +228 -0
  6. data/docs/API/COMMON.md +83 -0
  7. data/docs/API/NOTIFICATION.md +32 -0
  8. data/docs/API/POST_TOOL_USE.md +45 -0
  9. data/docs/API/PRE_COMPACT.md +39 -0
  10. data/docs/API/PRE_TOOL_USE.md +110 -0
  11. data/docs/API/SESSION_END.md +100 -0
  12. data/docs/API/SESSION_START.md +40 -0
  13. data/docs/API/STOP.md +47 -0
  14. data/docs/API/SUBAGENT_STOP.md +47 -0
  15. data/docs/API/USER_PROMPT_SUBMIT.md +47 -0
  16. data/{WHY.md → docs/WHY.md} +15 -8
  17. data/docs/external/claude-hooks-reference.md +34 -0
  18. data/example_dotclaude/hooks/entrypoints/pre_tool_use.rb +25 -0
  19. data/example_dotclaude/hooks/entrypoints/session_end.rb +35 -0
  20. data/example_dotclaude/hooks/entrypoints/user_prompt_submit.rb +17 -12
  21. data/example_dotclaude/hooks/handlers/pre_tool_use/github_guard.rb +253 -0
  22. data/example_dotclaude/hooks/handlers/session_end/cleanup_handler.rb +55 -0
  23. data/example_dotclaude/hooks/handlers/session_end/log_session_stats.rb +64 -0
  24. data/example_dotclaude/hooks/handlers/user_prompt_submit/append_rules.rb +3 -2
  25. data/example_dotclaude/hooks/handlers/user_prompt_submit/log_user_prompt.rb +2 -2
  26. data/example_dotclaude/plugins/README.md +175 -0
  27. data/example_dotclaude/settings.json +22 -0
  28. data/lib/claude_hooks/base.rb +16 -24
  29. data/lib/claude_hooks/cli.rb +75 -1
  30. data/lib/claude_hooks/logger.rb +0 -1
  31. data/lib/claude_hooks/output/base.rb +152 -0
  32. data/lib/claude_hooks/output/notification.rb +22 -0
  33. data/lib/claude_hooks/output/post_tool_use.rb +76 -0
  34. data/lib/claude_hooks/output/pre_compact.rb +20 -0
  35. data/lib/claude_hooks/output/pre_tool_use.rb +94 -0
  36. data/lib/claude_hooks/output/session_end.rb +24 -0
  37. data/lib/claude_hooks/output/session_start.rb +49 -0
  38. data/lib/claude_hooks/output/stop.rb +83 -0
  39. data/lib/claude_hooks/output/subagent_stop.rb +14 -0
  40. data/lib/claude_hooks/output/user_prompt_submit.rb +78 -0
  41. data/lib/claude_hooks/post_tool_use.rb +6 -12
  42. data/lib/claude_hooks/pre_tool_use.rb +0 -37
  43. data/lib/claude_hooks/session_end.rb +43 -0
  44. data/lib/claude_hooks/session_start.rb +0 -23
  45. data/lib/claude_hooks/stop.rb +8 -25
  46. data/lib/claude_hooks/user_prompt_submit.rb +0 -26
  47. data/lib/claude_hooks/version.rb +1 -1
  48. data/lib/claude_hooks.rb +15 -0
  49. metadata +37 -8
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ module Output
7
+ class PostToolUse < Base
8
+ # === DECISION ACCESSORS ===
9
+
10
+ def decision
11
+ @data['decision']
12
+ end
13
+
14
+ def reason
15
+ @data['reason'] || ''
16
+ end
17
+
18
+ def additional_context
19
+ hook_specific_output['additionalContext'] || ''
20
+ end
21
+
22
+ # === SEMANTIC HELPERS ===
23
+
24
+ def blocked?
25
+ decision == 'block'
26
+ end
27
+
28
+ # === EXIT CODE LOGIC ===
29
+ #
30
+ # PostToolUse hooks use the advanced JSON API with exit code 0.
31
+ # Per Anthropic guidance: when using structured JSON with decision/reason fields,
32
+ # always output to stdout with exit 0 (not stderr with exit 2).
33
+ # Reference: https://github.com/anthropics/claude-code/issues/10875
34
+ def exit_code
35
+ 0
36
+ end
37
+
38
+ # === OUTPUT STREAM LOGIC ===
39
+ #
40
+ # PostToolUse hooks always output to stdout when using the JSON API.
41
+ def output_stream
42
+ :stdout
43
+ end
44
+
45
+ # === MERGE HELPER ===
46
+
47
+ def self.merge(*outputs)
48
+ compacted_outputs = outputs.compact
49
+ return compacted_outputs.first if compacted_outputs.length == 1
50
+ return super(*outputs) if compacted_outputs.empty?
51
+
52
+ merged = super(*outputs)
53
+ merged_data = merged.data
54
+ contexts = []
55
+
56
+ compacted_outputs.each do |output|
57
+ output_data = output.respond_to?(:data) ? output.data : output
58
+ merged_data['decision'] = 'block' if output_data['decision'] == 'block'
59
+ merged_data['reason'] = [merged_data['reason'], output_data['reason']].compact.reject(&:empty?).join('; ')
60
+
61
+ context = output_data.dig('hookSpecificOutput', 'additionalContext')
62
+ contexts << context if context && !context.empty?
63
+ end
64
+
65
+ unless contexts.empty?
66
+ merged_data['hookSpecificOutput'] = {
67
+ 'hookEventName' => 'PostToolUse',
68
+ 'additionalContext' => contexts.join("\n\n")
69
+ }
70
+ end
71
+
72
+ new(merged_data)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ module Output
7
+ class PreCompact < Base
8
+ def exit_code
9
+ default_exit_code
10
+ end
11
+
12
+ # === MERGE HELPER ===
13
+
14
+ def self.merge(*outputs)
15
+ merged = super(*outputs)
16
+ new(merged.data)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ module Output
7
+ class PreToolUse < Base
8
+ # === PERMISSION DECISION ACCESSORS ===
9
+
10
+ def permission_decision
11
+ hook_specific_output['permissionDecision']
12
+ end
13
+
14
+ def permission_reason
15
+ hook_specific_output['permissionDecisionReason'] || ''
16
+ end
17
+
18
+ # === SEMANTIC HELPERS ===
19
+
20
+ def allowed?
21
+ permission_decision == 'allow'
22
+ end
23
+
24
+ def denied?
25
+ permission_decision == 'deny'
26
+ end
27
+ alias blocked? denied?
28
+
29
+ def should_ask_permission?
30
+ permission_decision == 'ask'
31
+ end
32
+
33
+ # === EXIT CODE LOGIC ===
34
+ #
35
+ # PreToolUse hooks use the advanced JSON API with exit code 0.
36
+ # Per Anthropic guidance: when using structured JSON with permissionDecision,
37
+ # always output to stdout with exit 0 (not stderr with exit 2).
38
+ # The permissionDecision field ('allow', 'deny', 'ask') controls behavior.
39
+ # Reference: https://github.com/anthropics/claude-code/issues/10875
40
+ def exit_code
41
+ 0
42
+ end
43
+
44
+ # === OUTPUT STREAM LOGIC ===
45
+ #
46
+ # PreToolUse hooks always output to stdout when using the JSON API.
47
+ def output_stream
48
+ :stdout
49
+ end
50
+
51
+ # === MERGE HELPER ===
52
+
53
+ def self.merge(*outputs)
54
+ compacted_outputs = outputs.compact
55
+ return compacted_outputs.first if compacted_outputs.length == 1
56
+ return super(*outputs) if compacted_outputs.empty?
57
+
58
+ merged = super(*outputs)
59
+ merged_data = merged.data
60
+
61
+ # PreToolUse specific merge: deny > ask > allow (most restrictive wins)
62
+ permission_decision = 'allow'
63
+ permission_reasons = []
64
+
65
+ compacted_outputs.each do |output|
66
+ output_data = output.respond_to?(:data) ? output.data : output
67
+
68
+ next unless output_data.dig('hookSpecificOutput', 'permissionDecision')
69
+
70
+ current_decision = output_data['hookSpecificOutput']['permissionDecision']
71
+ case current_decision
72
+ when 'deny'
73
+ permission_decision = 'deny'
74
+ when 'ask'
75
+ permission_decision = 'ask' unless permission_decision == 'deny'
76
+ end
77
+
78
+ reason = output_data.dig('hookSpecificOutput', 'permissionDecisionReason')
79
+ permission_reasons << reason if reason && !reason.empty?
80
+ end
81
+
82
+ merged_data['hookSpecificOutput'] ||= { 'hookEventName' => 'PreToolUse' }
83
+ merged_data['hookSpecificOutput']['permissionDecision'] = permission_decision
84
+ merged_data['hookSpecificOutput']['permissionDecisionReason'] = if permission_reasons.any?
85
+ permission_reasons.join('; ')
86
+ else
87
+ ''
88
+ end
89
+
90
+ new(merged_data)
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ module Output
7
+ # Note: SessionEnd hooks cannot block session termination - they're for cleanup only
8
+ class SessionEnd < Base
9
+ # === EXIT CODE LOGIC ===
10
+
11
+ # SessionEnd hooks always return 0 - they're for cleanup only
12
+ def exit_code
13
+ 0
14
+ end
15
+
16
+ # === MERGE HELPER ===
17
+
18
+ def self.merge(*outputs)
19
+ merged = super(*outputs)
20
+ new(merged.data)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ module Output
7
+ class SessionStart < Base
8
+ # === CONTEXT ACCESSORS ===
9
+
10
+ def additional_context
11
+ hook_specific_output['additionalContext'] || ''
12
+ end
13
+
14
+ # === EXIT CODE LOGIC ===
15
+
16
+ def exit_code
17
+ default_exit_code
18
+ end
19
+
20
+ # === MERGE HELPER ===
21
+
22
+ def self.merge(*outputs)
23
+ compacted_outputs = outputs.compact
24
+ return compacted_outputs.first if compacted_outputs.length == 1
25
+ return super(*outputs) if compacted_outputs.empty?
26
+
27
+ merged = super(*outputs)
28
+ merged_data = merged.data
29
+ contexts = []
30
+
31
+ compacted_outputs.each do |output|
32
+ output_data = output.respond_to?(:data) ? output.data : output
33
+ context = output_data.dig('hookSpecificOutput', 'additionalContext')
34
+ contexts << context if context && !context.empty?
35
+ end
36
+
37
+ # Set merged additional context
38
+ unless contexts.empty?
39
+ merged_data['hookSpecificOutput'] = {
40
+ 'hookEventName' => 'SessionStart',
41
+ 'additionalContext' => contexts.join("\n\n")
42
+ }
43
+ end
44
+
45
+ new(merged_data)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ module Output
7
+ # Note: In Stop hooks, 'decision: block' actually means "force Claude to continue"
8
+ # This is counterintuitive but matches Claude Code's expected behavior
9
+ class Stop < Base
10
+ # === DECISION ACCESSORS ===
11
+
12
+ def decision
13
+ @data['decision']
14
+ end
15
+
16
+ def reason
17
+ @data['reason'] || ''
18
+ end
19
+ alias_method :continue_instructions, :reason
20
+
21
+ # === SEMANTIC HELPERS ===
22
+
23
+ # Check if Claude should be forced to continue (decision == 'block')
24
+ # Note: 'block' in Stop hooks means "block the stopping", i.e., continue
25
+ def should_continue?
26
+ decision == 'block'
27
+ end
28
+
29
+ # Check if Claude should stop normally (decision != 'block')
30
+ def should_stop?
31
+ decision != 'block'
32
+ end
33
+
34
+ # === EXIT CODE LOGIC ===
35
+ #
36
+ # Stop hooks use the advanced JSON API with exit code 0.
37
+ # Per Anthropic guidance: when using structured JSON with decision/reason fields,
38
+ # always output to stdout with exit 0 (not stderr with exit 2).
39
+ # Reference: https://github.com/anthropics/claude-code/issues/10875
40
+ def exit_code
41
+ 0
42
+ end
43
+
44
+ # === OUTPUT STREAM LOGIC ===
45
+ #
46
+ # Stop hooks always output to stdout when using the JSON API.
47
+ # This follows the same pattern as PreToolUse hooks.
48
+ def output_stream
49
+ :stdout
50
+ end
51
+
52
+ # === MERGE HELPER ===
53
+
54
+ def self.merge(*outputs)
55
+ compacted_outputs = outputs.compact
56
+ return compacted_outputs.first if compacted_outputs.length == 1
57
+ return super(*outputs) if compacted_outputs.empty?
58
+
59
+ merged = super(*outputs)
60
+ merged_data = merged.data
61
+
62
+ # A blocking reason is actually a "continue instructions"
63
+ blocking_reasons = []
64
+
65
+ compacted_outputs.each do |output|
66
+ output_data = output.respond_to?(:data) ? output.data : output
67
+
68
+ # Handle decision - if any hook says 'block', respect that
69
+ if output_data['decision'] == 'block'
70
+ merged_data['decision'] = 'block'
71
+ reason = output_data['reason']
72
+ blocking_reasons << reason if reason && !reason.empty?
73
+ end
74
+ end
75
+
76
+ # Combine all blocking reasons / continue instructions
77
+ merged_data['reason'] = blocking_reasons.join('; ') unless blocking_reasons.empty?
78
+
79
+ new(merged_data)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'stop'
4
+
5
+ module ClaudeHooks
6
+ module Output
7
+ class SubagentStop < Stop
8
+ def self.merge(*outputs)
9
+ merged = super(*outputs)
10
+ new(merged.data)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ module Output
7
+ class UserPromptSubmit < Base
8
+ # === DECISION ACCESSORS ===
9
+
10
+ def decision
11
+ @data['decision']
12
+ end
13
+
14
+ def reason
15
+ @data['reason'] || ''
16
+ end
17
+
18
+ def additional_context
19
+ hook_specific_output['additionalContext'] || ''
20
+ end
21
+
22
+ # === SEMANTIC HELPERS ===
23
+
24
+ def blocked?
25
+ decision == 'block'
26
+ end
27
+
28
+ # === EXIT CODE LOGIC ===
29
+ #
30
+ # UserPromptSubmit hooks use the advanced JSON API with exit code 0.
31
+ # Per Anthropic guidance: when using structured JSON with decision/reason fields,
32
+ # always output to stdout with exit 0 (not stderr with exit 2).
33
+ # Reference: https://github.com/anthropics/claude-code/issues/10875
34
+ def exit_code
35
+ 0
36
+ end
37
+
38
+ # === OUTPUT STREAM LOGIC ===
39
+ #
40
+ # UserPromptSubmit hooks always output to stdout when using the JSON API.
41
+ def output_stream
42
+ :stdout
43
+ end
44
+
45
+ # === MERGE HELPER ===
46
+
47
+ def self.merge(*outputs)
48
+ compacted_outputs = outputs.compact
49
+ return compacted_outputs.first if compacted_outputs.length == 1
50
+ return super(*outputs) if compacted_outputs.empty?
51
+
52
+ merged = super(*outputs)
53
+ merged_data = merged.data
54
+
55
+ contexts = []
56
+
57
+ compacted_outputs.each do |output|
58
+ output_data = output.respond_to?(:data) ? output.data : output
59
+
60
+ merged_data['decision'] = 'block' if output_data['decision'] == 'block'
61
+ merged_data['reason'] = [merged_data['reason'], output_data['reason']].compact.reject(&:empty?).join('; ')
62
+
63
+ context = output_data.dig('hookSpecificOutput', 'additionalContext')
64
+ contexts << context if context && !context.empty?
65
+ end
66
+
67
+ unless contexts.empty?
68
+ merged_data['hookSpecificOutput'] = {
69
+ 'hookEventName' => 'UserPromptSubmit',
70
+ 'additionalContext' => contexts.join("\n\n")
71
+ }
72
+ end
73
+
74
+ new(merged_data)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -38,18 +38,12 @@ module ClaudeHooks
38
38
  @output_data['reason'] = nil
39
39
  end
40
40
 
41
- # === MERGE HELPER ===
42
-
43
- # Merge multiple PostToolUse hook results intelligently
44
- def self.merge_outputs(*outputs_data)
45
- merged = super(*outputs_data)
46
-
47
- outputs_data.compact.each do |output|
48
- merged['decision'] = 'block' if output['decision'] == 'block'
49
- merged['reason'] = [merged['reason'], output['reason']].compact.reject(&:empty?).join('; ')
50
- end
51
-
52
- merged
41
+ def add_additional_context!(context)
42
+ @output_data['hookSpecificOutput'] = {
43
+ 'hookEventName' => hook_event_name,
44
+ 'additionalContext' => context
45
+ }
53
46
  end
47
+ alias_method :add_context!, :add_additional_context!
54
48
  end
55
49
  end
@@ -47,42 +47,5 @@ module ClaudeHooks
47
47
  'permissionDecisionReason' => reason
48
48
  }
49
49
  end
50
-
51
- # === MERGE HELPER ===
52
-
53
- # Merge multiple PreToolUse hook results intelligently
54
- def self.merge_outputs(*outputs_data)
55
- merged = super(*outputs_data)
56
-
57
- # For PreToolUse: deny > ask > allow (most restrictive wins)
58
- permission_decision = 'allow'
59
- permission_reasons = []
60
-
61
- outputs_data.compact.each do |output|
62
- if output.dig('hookSpecificOutput', 'permissionDecision')
63
- current_decision = output['hookSpecificOutput']['permissionDecision']
64
- case current_decision
65
- when 'deny'
66
- permission_decision = 'deny'
67
- when 'ask'
68
- permission_decision = 'ask' unless permission_decision == 'deny'
69
- end
70
-
71
- if output['hookSpecificOutput']['permissionDecisionReason']
72
- permission_reasons << output['hookSpecificOutput']['permissionDecisionReason']
73
- end
74
- end
75
- end
76
-
77
- unless permission_reasons.empty?
78
- merged['hookSpecificOutput'] = {
79
- 'hookEventName' => hook_type,
80
- 'permissionDecision' => permission_decision,
81
- 'permissionDecisionReason' => permission_reasons.join('; ')
82
- }
83
- end
84
-
85
- merged
86
- end
87
50
  end
88
51
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ class SessionEnd < Base
7
+ def self.hook_type
8
+ 'SessionEnd'
9
+ end
10
+
11
+ def self.input_fields
12
+ %w[reason]
13
+ end
14
+
15
+ # === INPUT DATA ACCESS ===
16
+
17
+ def reason
18
+ @input_data['reason']
19
+ end
20
+
21
+ # === REASON HELPERS ===
22
+
23
+ # Check if session was cleared with /clear command
24
+ def cleared?
25
+ reason == 'clear'
26
+ end
27
+
28
+ # Check if user logged out
29
+ def logout?
30
+ reason == 'logout'
31
+ end
32
+
33
+ # Check if user exited while prompt input was visible
34
+ def prompt_input_exit?
35
+ reason == 'prompt_input_exit'
36
+ end
37
+
38
+ # Check if reason is unspecified or other
39
+ def other_reason?
40
+ !cleared? && !logout? && !prompt_input_exit?
41
+ end
42
+ end
43
+ end
@@ -31,28 +31,5 @@ module ClaudeHooks
31
31
  def empty_additional_context!
32
32
  @output_data['hookSpecificOutput'] = nil
33
33
  end
34
-
35
- # === MERGE HELPER ===
36
-
37
- # Merge multiple SessionStart hook results intelligently
38
- def self.merge_outputs(*outputs_data)
39
- merged = super(*outputs_data)
40
- contexts = []
41
-
42
- outputs_data.compact.each do |output|
43
- if output.dig('hookSpecificOutput', 'additionalContext')
44
- contexts << output['hookSpecificOutput']['additionalContext']
45
- end
46
- end
47
-
48
- unless contexts.empty?
49
- merged['hookSpecificOutput'] = {
50
- 'hookEventName' => hook_type,
51
- 'additionalContext' => contexts.join("\n\n")
52
- }
53
- end
54
-
55
- merged
56
- end
57
34
  end
58
35
  end
@@ -3,6 +3,14 @@
3
3
  require_relative 'base'
4
4
 
5
5
  module ClaudeHooks
6
+ # Stop hook for preventing Claude Code from stopping execution.
7
+ #
8
+ # When using continue_with_instructions!, this hook outputs JSON to stdout
9
+ # with exit code 0 (advanced JSON API approach).
10
+ #
11
+ # References:
12
+ # - https://github.com/anthropics/claude-code/issues/10875
13
+ # - https://github.com/gabriel-dehan/claude_hooks/issues/11
6
14
  class Stop < Base
7
15
  def self.hook_type
8
16
  'Stop'
@@ -32,30 +40,5 @@ module ClaudeHooks
32
40
  @output_data.delete('decision')
33
41
  @output_data.delete('reason')
34
42
  end
35
-
36
- # === MERGE HELPER ===
37
-
38
- # Merge multiple Stop hook results intelligently
39
- def self.merge_outputs(*outputs_data)
40
- merged = super(*outputs_data)
41
-
42
- # A blocking reason is actually a "continue instructions"
43
- blocking_reasons = []
44
-
45
- outputs_data.compact.each do |output|
46
- # Handle decision - if any hook says 'block', respect that
47
- if output['decision'] == 'block'
48
- merged['decision'] = 'block'
49
- blocking_reasons << output['reason'] if output['reason'] && !output['reason'].empty?
50
- end
51
- end
52
-
53
- # Combine all blocking reasons / continue instructions
54
- unless blocking_reasons.empty?
55
- merged['reason'] = blocking_reasons.join('; ')
56
- end
57
-
58
- merged
59
- end
60
43
  end
61
44
  end
@@ -43,31 +43,5 @@ module ClaudeHooks
43
43
  @output_data['decision'] = nil
44
44
  @output_data['reason'] = nil
45
45
  end
46
-
47
- # === MERGE HELPER ===
48
-
49
- # Merge multiple UserPromptSubmit hook results intelligently
50
- def self.merge_outputs(*outputs_data)
51
- merged = super(*outputs_data)
52
- contexts = []
53
-
54
- outputs_data.compact.each do |output|
55
- merged['decision'] = 'block' if output['decision'] == 'block'
56
- merged['reason'] = [merged['reason'], output['reason']].compact.reject(&:empty?).join('; ')
57
-
58
- if output.dig('hookSpecificOutput', 'additionalContext')
59
- contexts << output['hookSpecificOutput']['additionalContext']
60
- end
61
- end
62
-
63
- unless contexts.empty?
64
- merged['hookSpecificOutput'] = {
65
- 'hookEventName' => hook_type,
66
- 'additionalContext' => contexts.join("\n\n")
67
- }
68
- end
69
-
70
- merged
71
- end
72
46
  end
73
47
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeHooks
4
- VERSION = "0.2.1"
4
+ VERSION = "1.0.2"
5
5
  end