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
@@ -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
@@ -32,30 +32,5 @@ module ClaudeHooks
32
32
  @output_data.delete('decision')
33
33
  @output_data.delete('reason')
34
34
  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
35
  end
61
36
  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.0"
4
+ VERSION = "1.0.0"
5
5
  end
data/lib/claude_hooks.rb CHANGED
@@ -5,6 +5,8 @@ require_relative "claude_hooks/configuration"
5
5
  require_relative "claude_hooks/logger"
6
6
  require_relative "claude_hooks/base"
7
7
  require_relative "claude_hooks/cli"
8
+
9
+ # Hook classes
8
10
  require_relative "claude_hooks/user_prompt_submit"
9
11
  require_relative "claude_hooks/pre_tool_use"
10
12
  require_relative "claude_hooks/post_tool_use"
@@ -13,6 +15,19 @@ require_relative "claude_hooks/stop"
13
15
  require_relative "claude_hooks/subagent_stop"
14
16
  require_relative "claude_hooks/pre_compact"
15
17
  require_relative "claude_hooks/session_start"
18
+ require_relative "claude_hooks/session_end"
19
+
20
+ # Output classes
21
+ require_relative "claude_hooks/output/base"
22
+ require_relative "claude_hooks/output/user_prompt_submit"
23
+ require_relative "claude_hooks/output/pre_tool_use"
24
+ require_relative "claude_hooks/output/post_tool_use"
25
+ require_relative "claude_hooks/output/notification"
26
+ require_relative "claude_hooks/output/stop"
27
+ require_relative "claude_hooks/output/subagent_stop"
28
+ require_relative "claude_hooks/output/pre_compact"
29
+ require_relative "claude_hooks/output/session_start"
30
+ require_relative "claude_hooks/output/session_end"
16
31
 
17
32
  module ClaudeHooks
18
33
  class Error < StandardError; end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: claude_hooks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriel Dehan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-08-21 00:00:00.000000000 Z
11
+ date: 2025-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -39,19 +39,19 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rspec
42
+ name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '3.0'
47
+ version: '5.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '3.0'
54
+ version: '5.0'
55
55
  description: A Ruby DSL framework for creating Claude Code hooks with composable hook
56
56
  scripts that enable teams to easily implement logging, security checks, and workflow
57
57
  automation.
@@ -63,10 +63,24 @@ extra_rdoc_files: []
63
63
  files:
64
64
  - CHANGELOG.md
65
65
  - README.md
66
- - WHY.md
67
66
  - claude_hooks.gemspec
67
+ - docs/API/COMMON.md
68
+ - docs/API/NOTIFICATION.md
69
+ - docs/API/POST_TOOL_USE.md
70
+ - docs/API/PRE_COMPACT.md
71
+ - docs/API/PRE_TOOL_USE.md
72
+ - docs/API/SESSION_END.md
73
+ - docs/API/SESSION_START.md
74
+ - docs/API/STOP.md
75
+ - docs/API/SUBAGENT_STOP.md
76
+ - docs/API/USER_PROMPT_SUBMIT.md
77
+ - docs/OUTPUT_MIGRATION_GUIDE.md
78
+ - docs/WHY.md
68
79
  - example_dotclaude/commands/.gitkeep
80
+ - example_dotclaude/hooks/entrypoints/session_end.rb
69
81
  - example_dotclaude/hooks/entrypoints/user_prompt_submit.rb
82
+ - example_dotclaude/hooks/handlers/session_end/cleanup_handler.rb
83
+ - example_dotclaude/hooks/handlers/session_end/log_session_stats.rb
70
84
  - example_dotclaude/hooks/handlers/user_prompt_submit/append_rules.rb
71
85
  - example_dotclaude/hooks/handlers/user_prompt_submit/log_user_prompt.rb
72
86
  - example_dotclaude/settings.json
@@ -76,9 +90,20 @@ files:
76
90
  - lib/claude_hooks/configuration.rb
77
91
  - lib/claude_hooks/logger.rb
78
92
  - lib/claude_hooks/notification.rb
93
+ - lib/claude_hooks/output/base.rb
94
+ - lib/claude_hooks/output/notification.rb
95
+ - lib/claude_hooks/output/post_tool_use.rb
96
+ - lib/claude_hooks/output/pre_compact.rb
97
+ - lib/claude_hooks/output/pre_tool_use.rb
98
+ - lib/claude_hooks/output/session_end.rb
99
+ - lib/claude_hooks/output/session_start.rb
100
+ - lib/claude_hooks/output/stop.rb
101
+ - lib/claude_hooks/output/subagent_stop.rb
102
+ - lib/claude_hooks/output/user_prompt_submit.rb
79
103
  - lib/claude_hooks/post_tool_use.rb
80
104
  - lib/claude_hooks/pre_compact.rb
81
105
  - lib/claude_hooks/pre_tool_use.rb
106
+ - lib/claude_hooks/session_end.rb
82
107
  - lib/claude_hooks/session_start.rb
83
108
  - lib/claude_hooks/stop.rb
84
109
  - lib/claude_hooks/subagent_stop.rb
@@ -100,14 +125,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
100
125
  requirements:
101
126
  - - ">="
102
127
  - !ruby/object:Gem::Version
103
- version: 2.7.0
128
+ version: 3.2.0
104
129
  required_rubygems_version: !ruby/object:Gem::Requirement
105
130
  requirements:
106
131
  - - ">="
107
132
  - !ruby/object:Gem::Version
108
133
  version: '0'
109
134
  requirements: []
110
- rubygems_version: 3.2.33
135
+ rubygems_version: 3.4.19
111
136
  signing_key:
112
137
  specification_version: 4
113
138
  summary: Ruby DSL for creating Claude Code hooks
File without changes