claude_hooks 0.2.0 → 1.0.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +98 -1
  3. data/README.md +221 -362
  4. data/claude_hooks.gemspec +2 -2
  5. data/docs/API/COMMON.md +83 -0
  6. data/docs/API/NOTIFICATION.md +32 -0
  7. data/docs/API/POST_TOOL_USE.md +45 -0
  8. data/docs/API/PRE_COMPACT.md +39 -0
  9. data/docs/API/PRE_TOOL_USE.md +110 -0
  10. data/docs/API/SESSION_END.md +100 -0
  11. data/docs/API/SESSION_START.md +40 -0
  12. data/docs/API/STOP.md +47 -0
  13. data/docs/API/SUBAGENT_STOP.md +47 -0
  14. data/docs/API/USER_PROMPT_SUBMIT.md +47 -0
  15. data/docs/OUTPUT_MIGRATION_GUIDE.md +228 -0
  16. data/example_dotclaude/hooks/entrypoints/session_end.rb +35 -0
  17. data/example_dotclaude/hooks/entrypoints/user_prompt_submit.rb +17 -12
  18. data/example_dotclaude/hooks/handlers/session_end/cleanup_handler.rb +55 -0
  19. data/example_dotclaude/hooks/handlers/session_end/log_session_stats.rb +64 -0
  20. data/example_dotclaude/hooks/handlers/user_prompt_submit/append_rules.rb +11 -9
  21. data/example_dotclaude/hooks/handlers/user_prompt_submit/log_user_prompt.rb +2 -14
  22. data/example_dotclaude/settings.json +11 -0
  23. data/lib/claude_hooks/base.rb +16 -24
  24. data/lib/claude_hooks/cli.rb +75 -1
  25. data/lib/claude_hooks/configuration.rb +9 -9
  26. data/lib/claude_hooks/logger.rb +0 -1
  27. data/lib/claude_hooks/output/base.rb +152 -0
  28. data/lib/claude_hooks/output/notification.rb +22 -0
  29. data/lib/claude_hooks/output/post_tool_use.rb +68 -0
  30. data/lib/claude_hooks/output/pre_compact.rb +20 -0
  31. data/lib/claude_hooks/output/pre_tool_use.rb +97 -0
  32. data/lib/claude_hooks/output/session_end.rb +24 -0
  33. data/lib/claude_hooks/output/session_start.rb +49 -0
  34. data/lib/claude_hooks/output/stop.rb +76 -0
  35. data/lib/claude_hooks/output/subagent_stop.rb +14 -0
  36. data/lib/claude_hooks/output/user_prompt_submit.rb +71 -0
  37. data/lib/claude_hooks/post_tool_use.rb +6 -12
  38. data/lib/claude_hooks/pre_tool_use.rb +0 -37
  39. data/lib/claude_hooks/session_end.rb +43 -0
  40. data/lib/claude_hooks/session_start.rb +0 -23
  41. data/lib/claude_hooks/stop.rb +0 -25
  42. data/lib/claude_hooks/user_prompt_submit.rb +0 -26
  43. data/lib/claude_hooks/version.rb +1 -1
  44. data/lib/claude_hooks.rb +15 -0
  45. metadata +33 -8
  46. /data/{WHY.md → docs/WHY.md} +0 -0
@@ -122,7 +122,7 @@ module ClaudeHooks
122
122
  # Check if we have a config value for this method
123
123
  env_key = method_name.to_s.upcase
124
124
  config_key = snake_case_to_camel_case(method_name.to_s)
125
-
125
+
126
126
  !get_config_value(env_key, config_key).nil? || super
127
127
  end
128
128
 
@@ -155,10 +155,10 @@ module ClaudeHooks
155
155
  def load_config
156
156
  # Load and merge config files from both locations
157
157
  merged_file_config = load_and_merge_config_files
158
-
158
+
159
159
  # Merge with ENV variables
160
160
  env_config = load_env_config
161
-
161
+
162
162
  # ENV variables take precedence over file configs
163
163
  merged_file_config.merge(env_config)
164
164
  end
@@ -166,10 +166,10 @@ module ClaudeHooks
166
166
  def load_and_merge_config_files
167
167
  home_config = load_config_file_from_path(home_config_file_path)
168
168
  project_config = load_config_file_from_path(project_config_file_path) if project_config_file_path
169
-
169
+
170
170
  # Determine merge strategy
171
- merge_strategy = ENV['CLAUDE_HOOKS_CONFIG_MERGE_STRATEGY'] || 'project'
172
-
171
+ merge_strategy = ENV['RUBY_CLAUDE_HOOKS_CONFIG_MERGE_STRATEGY'] || 'project'
172
+
173
173
  if project_config && merge_strategy == 'project'
174
174
  # Project config takes precedence
175
175
  home_config.merge(project_config)
@@ -205,15 +205,15 @@ module ClaudeHooks
205
205
 
206
206
  def load_env_config
207
207
  env_config = {}
208
-
208
+
209
209
  ENV.each do |key, value|
210
210
  next unless key.start_with?(ENV_PREFIX)
211
-
211
+
212
212
  # Remove prefix and convert to config key format
213
213
  config_key = env_key_to_config_key(key.sub(ENV_PREFIX, ''))
214
214
  env_config[config_key] = value
215
215
  end
216
-
216
+
217
217
  env_config
218
218
  end
219
219
 
@@ -5,7 +5,6 @@ require_relative 'configuration'
5
5
 
6
6
  module ClaudeHooks
7
7
  # Session-based logger for Claude Code hooks
8
- # Provides both single-line and multiline block-based logging
9
8
  class Logger
10
9
  def initialize(session_id, source)
11
10
  @session_id = session_id
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module ClaudeHooks
6
+ module Output
7
+ # Base class for all Claude Code hook output handlers
8
+ # Handles common functionality like continue/stop logic, output streams, and exit codes
9
+ class Base
10
+ attr_reader :data
11
+
12
+ def initialize(data)
13
+ @data = data || {}
14
+ end
15
+
16
+ # === COMMON FIELD ACCESSORS ===
17
+
18
+ # Check if Claude should continue processing
19
+ def continue?
20
+ @data['continue'] != false
21
+ end
22
+
23
+ # Get the stop reason if continue is false
24
+ def stop_reason
25
+ @data['stopReason'] || ''
26
+ end
27
+
28
+ # Check if output should be suppressed from transcript
29
+ def suppress_output?
30
+ @data['suppressOutput'] == true
31
+ end
32
+
33
+ # Get the system message (if any)
34
+ def system_message
35
+ @data['systemMessage'] || ''
36
+ end
37
+
38
+ # Get the hook-specific output data
39
+ def hook_specific_output
40
+ @data['hookSpecificOutput'] || {}
41
+ end
42
+
43
+ # === JSON SERIALIZATION ===
44
+
45
+ # Convert to JSON string (same as existing stringify_output)
46
+ def to_json(*args)
47
+ JSON.generate(@data, *args)
48
+ end
49
+ alias_method :stringify, :to_json
50
+
51
+ # === EXECUTION CONTROL ===
52
+
53
+ # Main execution method - handles output and exits with correct code
54
+ def output_and_exit
55
+ stream = output_stream
56
+ code = exit_code
57
+
58
+ case stream
59
+ when :stdout
60
+ $stdout.puts to_json
61
+ when :stderr
62
+ $stderr.puts to_json
63
+ else
64
+ raise "Unknown output stream: #{stream}"
65
+ end
66
+
67
+ exit code
68
+ end
69
+
70
+ # === ABSTRACT METHODS ===
71
+ # These must be implemented by subclasses
72
+
73
+ # Determine the exit code based on hook-specific logic
74
+ def exit_code
75
+ raise NotImplementedError, "Subclasses must implement exit_code"
76
+ end
77
+
78
+ # Determine the output stream (:stdout or :stderr)
79
+ def output_stream
80
+ default_output_stream
81
+ end
82
+
83
+ # === MERGE HELPER ===
84
+
85
+ # Base merge method - handles common fields like continue, stopReason, suppressOutput
86
+ # Subclasses should call super and add their specific logic
87
+ def self.merge(*outputs)
88
+ compacted_outputs = outputs.compact
89
+
90
+ merged_data = {
91
+ 'continue' => true,
92
+ 'stopReason' => '',
93
+ 'suppressOutput' => false,
94
+ 'systemMessage' => ''
95
+ }
96
+
97
+ return compacted_outputs.first if compacted_outputs.length == 1
98
+ return self.new(merged_data) if compacted_outputs.empty?
99
+
100
+ # Apply base merge logic
101
+ compacted_outputs.each do |output|
102
+ output_data = output.respond_to?(:data) ? output.data : output
103
+
104
+ merged_data['continue'] = false if output_data['continue'] == false
105
+ merged_data['suppressOutput'] = true if output_data['suppressOutput'] == true
106
+ merged_data['stopReason'] = [merged_data['stopReason'], output_data['stopReason']].compact.reject(&:empty?).join('; ')
107
+ merged_data['systemMessage'] = [merged_data['systemMessage'], output_data['systemMessage']].compact.reject(&:empty?).join('; ')
108
+ end
109
+
110
+ self.new(merged_data)
111
+ end
112
+
113
+ # === FACTORY ===
114
+
115
+ # Factory method to create the correct output class for a given hook type
116
+ def self.for_hook_type(hook_type, data)
117
+ case hook_type
118
+ when 'UserPromptSubmit'
119
+ UserPromptSubmit.new(data)
120
+ when 'PreToolUse'
121
+ PreToolUse.new(data)
122
+ when 'PostToolUse'
123
+ PostToolUse.new(data)
124
+ when 'Stop'
125
+ Stop.new(data)
126
+ when 'SubagentStop'
127
+ SubagentStop.new(data)
128
+ when 'Notification'
129
+ Notification.new(data)
130
+ when 'SessionStart'
131
+ SessionStart.new(data)
132
+ when 'SessionEnd'
133
+ SessionEnd.new(data)
134
+ when 'PreCompact'
135
+ PreCompact.new(data)
136
+ else
137
+ raise ArgumentError, "Unknown hook type: #{hook_type}"
138
+ end
139
+ end
140
+
141
+ protected
142
+
143
+ def default_exit_code
144
+ continue? ? 0 : 2
145
+ end
146
+
147
+ def default_output_stream
148
+ exit_code == 2 ? :stderr : :stdout
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ module Output
7
+ class Notification < Base
8
+ # === EXIT CODE LOGIC ===
9
+
10
+ def exit_code
11
+ default_exit_code
12
+ end
13
+
14
+ # === MERGE HELPER ===
15
+
16
+ def self.merge(*outputs)
17
+ merged = super(*outputs)
18
+ new(merged.data)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,68 @@
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
+ def exit_code
31
+ return 2 unless continue?
32
+ return 2 if blocked?
33
+
34
+ 0
35
+ end
36
+
37
+ # === MERGE HELPER ===
38
+
39
+ def self.merge(*outputs)
40
+ compacted_outputs = outputs.compact
41
+ return compacted_outputs.first if compacted_outputs.length == 1
42
+ return super(*outputs) if compacted_outputs.empty?
43
+
44
+ merged = super(*outputs)
45
+ merged_data = merged.data
46
+ contexts = []
47
+
48
+ compacted_outputs.each do |output|
49
+ output_data = output.respond_to?(:data) ? output.data : output
50
+ merged_data['decision'] = 'block' if output_data['decision'] == 'block'
51
+ merged_data['reason'] = [merged_data['reason'], output_data['reason']].compact.reject(&:empty?).join('; ')
52
+
53
+ context = output_data.dig('hookSpecificOutput', 'additionalContext')
54
+ contexts << context if context && !context.empty?
55
+ end
56
+
57
+ unless contexts.empty?
58
+ merged_data['hookSpecificOutput'] = {
59
+ 'hookEventName' => 'PostToolUse',
60
+ 'additionalContext' => contexts.join("\n\n")
61
+ }
62
+ end
63
+
64
+ new(merged_data)
65
+ end
66
+ end
67
+ end
68
+ 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,97 @@
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_method :blocked?, :denied?
28
+
29
+ def should_ask_permission?
30
+ permission_decision == 'ask'
31
+ end
32
+
33
+ # === EXIT CODE LOGIC ===
34
+
35
+ # Priority: continue false > permission decision
36
+ def exit_code
37
+ return 2 unless continue?
38
+
39
+ case permission_decision
40
+ when 'deny'
41
+ 2
42
+ when 'ask'
43
+ 1
44
+ else # allow
45
+ 0
46
+ end
47
+ end
48
+
49
+ # STDOUT always works for PreToolUse
50
+ def output_stream
51
+ :stdout
52
+ end
53
+
54
+ # === MERGE HELPER ===
55
+
56
+ def self.merge(*outputs)
57
+ compacted_outputs = outputs.compact
58
+ return compacted_outputs.first if compacted_outputs.length == 1
59
+ return super(*outputs) if compacted_outputs.empty?
60
+
61
+ merged = super(*outputs)
62
+ merged_data = merged.data
63
+
64
+ # PreToolUse specific merge: deny > ask > allow (most restrictive wins)
65
+ permission_decision = 'allow'
66
+ permission_reasons = []
67
+
68
+ compacted_outputs.each do |output|
69
+ output_data = output.respond_to?(:data) ? output.data : output
70
+
71
+ if output_data.dig('hookSpecificOutput', 'permissionDecision')
72
+ current_decision = output_data['hookSpecificOutput']['permissionDecision']
73
+ case current_decision
74
+ when 'deny'
75
+ permission_decision = 'deny'
76
+ when 'ask'
77
+ permission_decision = 'ask' unless permission_decision == 'deny'
78
+ end
79
+
80
+ reason = output_data.dig('hookSpecificOutput', 'permissionDecisionReason')
81
+ permission_reasons << reason if reason && !reason.empty?
82
+ end
83
+ end
84
+
85
+ merged_data['hookSpecificOutput'] ||= { 'hookEventName' => 'PreToolUse' }
86
+ merged_data['hookSpecificOutput']['permissionDecision'] = permission_decision
87
+ if permission_reasons.any?
88
+ merged_data['hookSpecificOutput']['permissionDecisionReason'] = permission_reasons.join('; ')
89
+ else
90
+ merged_data['hookSpecificOutput']['permissionDecisionReason'] = ''
91
+ end
92
+
93
+ new(merged_data)
94
+ end
95
+ end
96
+ end
97
+ 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,76 @@
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
+ def exit_code
37
+ # For Stop hooks: we assume 'continue' means continue to stop
38
+ return 0 unless continue?
39
+ # For Stop hooks: decision 'block' means force continue (exit 2)
40
+ return 2 if should_continue?
41
+
42
+ 0
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
+ # A blocking reason is actually a "continue instructions"
56
+ blocking_reasons = []
57
+
58
+ compacted_outputs.each do |output|
59
+ output_data = output.respond_to?(:data) ? output.data : output
60
+
61
+ # Handle decision - if any hook says 'block', respect that
62
+ if output_data['decision'] == 'block'
63
+ merged_data['decision'] = 'block'
64
+ reason = output_data['reason']
65
+ blocking_reasons << reason if reason && !reason.empty?
66
+ end
67
+ end
68
+
69
+ # Combine all blocking reasons / continue instructions
70
+ merged_data['reason'] = blocking_reasons.join('; ') unless blocking_reasons.empty?
71
+
72
+ new(merged_data)
73
+ end
74
+ end
75
+ end
76
+ 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,71 @@
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
+ def exit_code
31
+ return 2 unless continue?
32
+ return 2 if blocked?
33
+
34
+ 0
35
+ end
36
+
37
+ # === MERGE HELPER ===
38
+
39
+ def self.merge(*outputs)
40
+ compacted_outputs = outputs.compact
41
+ return compacted_outputs.first if compacted_outputs.length == 1
42
+ return super(*outputs) if compacted_outputs.empty?
43
+
44
+ merged = super(*outputs)
45
+ merged_data = merged.data
46
+
47
+ contexts = []
48
+ reasons = []
49
+
50
+ compacted_outputs.each do |output|
51
+ output_data = output.respond_to?(:data) ? output.data : output
52
+
53
+ merged_data['decision'] = 'block' if output_data['decision'] == 'block'
54
+ merged_data['reason'] = [merged_data['reason'], output_data['reason']].compact.reject(&:empty?).join('; ')
55
+
56
+ context = output_data.dig('hookSpecificOutput', 'additionalContext')
57
+ contexts << context if context && !context.empty?
58
+ end
59
+
60
+ unless contexts.empty?
61
+ merged_data['hookSpecificOutput'] = {
62
+ 'hookEventName' => 'UserPromptSubmit',
63
+ 'additionalContext' => contexts.join("\n\n")
64
+ }
65
+ end
66
+
67
+ new(merged_data)
68
+ end
69
+ end
70
+ end
71
+ 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