claude_hooks 1.0.2 → 1.2.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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/skills/ci-monitoring/SKILL.md +103 -0
  3. data/.agents/skills/run-tests/SKILL.md +45 -0
  4. data/.claude/auto-fix.md +7 -0
  5. data/.claude/settings.local.json +21 -0
  6. data/AGENTS.md +46 -0
  7. data/CHANGELOG.md +93 -0
  8. data/README.md +159 -21
  9. data/docs/1.1.0_UPGRADE_GUIDE.md +269 -0
  10. data/docs/API/COMMON.md +7 -0
  11. data/docs/API/CONFIG_CHANGE.md +39 -0
  12. data/docs/API/CWD_CHANGED.md +34 -0
  13. data/docs/API/ELICITATION.md +44 -0
  14. data/docs/API/ELICITATION_RESULT.md +43 -0
  15. data/docs/API/FILE_CHANGED.md +37 -0
  16. data/docs/API/INSTRUCTIONS_LOADED.md +30 -0
  17. data/docs/API/MESSAGE_DISPLAY.md +38 -0
  18. data/docs/API/NOTIFICATION.md +1 -0
  19. data/docs/API/PERMISSION_DENIED.md +37 -0
  20. data/docs/API/PERMISSION_REQUEST.md +199 -0
  21. data/docs/API/POST_COMPACT.md +30 -0
  22. data/docs/API/POST_TOOL_BATCH.md +39 -0
  23. data/docs/API/POST_TOOL_USE.md +1 -0
  24. data/docs/API/POST_TOOL_USE_FAILURE.md +31 -0
  25. data/docs/API/PRE_COMPACT.md +10 -2
  26. data/docs/API/PRE_TOOL_USE.md +9 -1
  27. data/docs/API/SETUP.md +37 -0
  28. data/docs/API/STOP_FAILURE.md +19 -0
  29. data/docs/API/SUBAGENT_START.md +40 -0
  30. data/docs/API/TASK_COMPLETED.md +43 -0
  31. data/docs/API/TASK_CREATED.md +43 -0
  32. data/docs/API/TEAMMATE_IDLE.md +40 -0
  33. data/docs/API/USER_PROMPT_EXPANSION.md +43 -0
  34. data/docs/API/WORKTREE_CREATE.md +39 -0
  35. data/docs/API/WORKTREE_REMOVE.md +33 -0
  36. data/docs/PROMPT_BASED_HOOKS.md +386 -0
  37. data/docs/external/claude-hooks-reference.md +2846 -18
  38. data/docs/mitts/ideabox.md +12 -0
  39. data/docs/mitts/prd.md +417 -0
  40. data/docs/mitts/setup.md +303 -0
  41. data/docs/mitts/task.md +44 -0
  42. data/lib/claude_hooks/base.rb +30 -1
  43. data/lib/claude_hooks/config_change.rb +28 -0
  44. data/lib/claude_hooks/cwd_changed.rb +28 -0
  45. data/lib/claude_hooks/elicitation.rb +61 -0
  46. data/lib/claude_hooks/elicitation_result.rb +57 -0
  47. data/lib/claude_hooks/file_changed.rb +41 -0
  48. data/lib/claude_hooks/instructions_loaded.rb +23 -0
  49. data/lib/claude_hooks/message_display.rb +58 -0
  50. data/lib/claude_hooks/notification.rb +5 -1
  51. data/lib/claude_hooks/output/base.rb +48 -1
  52. data/lib/claude_hooks/output/config_change.rb +47 -0
  53. data/lib/claude_hooks/output/cwd_changed.rb +42 -0
  54. data/lib/claude_hooks/output/elicitation.rb +67 -0
  55. data/lib/claude_hooks/output/elicitation_result.rb +63 -0
  56. data/lib/claude_hooks/output/file_changed.rb +42 -0
  57. data/lib/claude_hooks/output/instructions_loaded.rb +19 -0
  58. data/lib/claude_hooks/output/message_display.rb +49 -0
  59. data/lib/claude_hooks/output/permission_denied.rb +42 -0
  60. data/lib/claude_hooks/output/permission_request.rb +120 -0
  61. data/lib/claude_hooks/output/post_compact.rb +18 -0
  62. data/lib/claude_hooks/output/post_tool_batch.rb +51 -0
  63. data/lib/claude_hooks/output/post_tool_use.rb +12 -0
  64. data/lib/claude_hooks/output/post_tool_use_failure.rb +42 -0
  65. data/lib/claude_hooks/output/pre_compact.rb +34 -3
  66. data/lib/claude_hooks/output/pre_tool_use.rb +26 -2
  67. data/lib/claude_hooks/output/session_start.rb +30 -8
  68. data/lib/claude_hooks/output/setup.rb +42 -0
  69. data/lib/claude_hooks/output/stop.rb +15 -0
  70. data/lib/claude_hooks/output/stop_failure.rb +19 -0
  71. data/lib/claude_hooks/output/subagent_start.rb +42 -0
  72. data/lib/claude_hooks/output/task_completed.rb +18 -0
  73. data/lib/claude_hooks/output/task_created.rb +18 -0
  74. data/lib/claude_hooks/output/teammate_idle.rb +18 -0
  75. data/lib/claude_hooks/output/user_prompt_expansion.rb +51 -0
  76. data/lib/claude_hooks/output/worktree_create.rb +60 -0
  77. data/lib/claude_hooks/output/worktree_remove.rb +19 -0
  78. data/lib/claude_hooks/permission_denied.rb +41 -0
  79. data/lib/claude_hooks/permission_request.rb +63 -0
  80. data/lib/claude_hooks/post_compact.rb +23 -0
  81. data/lib/claude_hooks/post_tool_batch.rb +59 -0
  82. data/lib/claude_hooks/post_tool_use.rb +15 -1
  83. data/lib/claude_hooks/post_tool_use_failure.rb +45 -0
  84. data/lib/claude_hooks/pre_compact.rb +7 -0
  85. data/lib/claude_hooks/pre_tool_use.rb +34 -1
  86. data/lib/claude_hooks/session_start.rb +28 -0
  87. data/lib/claude_hooks/setup.rb +25 -0
  88. data/lib/claude_hooks/stop.rb +17 -0
  89. data/lib/claude_hooks/stop_failure.rb +27 -0
  90. data/lib/claude_hooks/subagent_start.rb +23 -0
  91. data/lib/claude_hooks/subagent_stop.rb +5 -1
  92. data/lib/claude_hooks/task_completed.rb +35 -0
  93. data/lib/claude_hooks/task_created.rb +35 -0
  94. data/lib/claude_hooks/teammate_idle.rb +23 -0
  95. data/lib/claude_hooks/user_prompt_expansion.rb +45 -0
  96. data/lib/claude_hooks/version.rb +1 -1
  97. data/lib/claude_hooks/worktree_create.rb +27 -0
  98. data/lib/claude_hooks/worktree_remove.rb +19 -0
  99. data/lib/claude_hooks.rb +42 -0
  100. metadata +76 -2
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ class PermissionRequest < Base
7
+ def self.hook_type
8
+ 'PermissionRequest'
9
+ end
10
+
11
+ def self.input_fields
12
+ %w[tool_name tool_input tool_use_id]
13
+ end
14
+
15
+ # === INPUT DATA ACCESS ===
16
+
17
+ def tool_name
18
+ @input_data['tool_name']
19
+ end
20
+
21
+ def tool_input
22
+ @input_data['tool_input']
23
+ end
24
+
25
+ def tool_use_id
26
+ @input_data['tool_use_id'] || @input_data['toolUseId']
27
+ end
28
+
29
+ def permission_suggestions
30
+ @input_data['permissionSuggestions'] || @input_data['permission_suggestions'] || []
31
+ end
32
+
33
+ # === OUTPUT DATA HELPERS ===
34
+
35
+ def allow_permission!(reason = '')
36
+ @output_data['hookSpecificOutput'] = {
37
+ 'hookEventName' => hook_event_name,
38
+ 'decision' => { 'behavior' => 'allow' }
39
+ }
40
+ @output_data['hookSpecificOutput']['decision']['message'] = reason unless reason.empty?
41
+ end
42
+
43
+ def deny_permission!(reason = '', interrupt: nil)
44
+ decision = { 'behavior' => 'deny' }
45
+ decision['message'] = reason unless reason.empty?
46
+ decision['interrupt'] = interrupt unless interrupt.nil?
47
+ @output_data['hookSpecificOutput'] = {
48
+ 'hookEventName' => hook_event_name,
49
+ 'decision' => decision
50
+ }
51
+ end
52
+
53
+ def update_input_and_allow!(updated_input, reason = '', updated_permissions: nil)
54
+ decision = { 'behavior' => 'allow', 'updatedInput' => updated_input }
55
+ decision['message'] = reason unless reason.empty?
56
+ decision['updatedPermissions'] = updated_permissions unless updated_permissions.nil?
57
+ @output_data['hookSpecificOutput'] = {
58
+ 'hookEventName' => hook_event_name,
59
+ 'decision' => decision
60
+ }
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ class PostCompact < Base
7
+ def self.hook_type
8
+ 'PostCompact'
9
+ end
10
+
11
+ def self.input_fields
12
+ %w[trigger compact_summary]
13
+ end
14
+
15
+ def trigger
16
+ @input_data['trigger']
17
+ end
18
+
19
+ def compact_summary
20
+ @input_data['compact_summary'] || @input_data['compactSummary']
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ class PostToolBatch < Base
7
+ def self.hook_type
8
+ 'PostToolBatch'
9
+ end
10
+
11
+ def self.input_fields
12
+ %w[tool_calls]
13
+ end
14
+
15
+ def tool_calls
16
+ @input_data['tool_calls'] || @input_data['toolCalls'] || []
17
+ end
18
+
19
+ # Convenience partitions over tool_calls. Entries carry a `tool_response`
20
+ # rather than an explicit success flag, so success is derived from the
21
+ # presence of a response that isn't flagged as an error. When your payload
22
+ # shape differs, use the raw `tool_calls` array instead.
23
+ def succeeded_calls
24
+ tool_calls.reject { |call| failed_call?(call) }
25
+ end
26
+
27
+ def failed_calls
28
+ tool_calls.select { |call| failed_call?(call) }
29
+ end
30
+
31
+ def block!(reason = '')
32
+ @output_data['decision'] = 'block'
33
+ @output_data['reason'] = reason
34
+ end
35
+
36
+ def add_additional_context!(context)
37
+ @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
38
+ @output_data['hookSpecificOutput']['additionalContext'] = context
39
+ end
40
+
41
+ private
42
+
43
+ # A call is considered failed when its response is missing or carries an
44
+ # error marker (`is_error`/`isError` true, or an `error` key).
45
+ def failed_call?(call)
46
+ return true unless call.is_a?(Hash)
47
+
48
+ response = call['tool_response'] || call['toolResponse']
49
+ return true if response.nil?
50
+
51
+ if response.is_a?(Hash)
52
+ return true if response['is_error'] == true || response['isError'] == true
53
+ return true if response.key?('error') && !response['error'].nil?
54
+ end
55
+
56
+ false
57
+ end
58
+ end
59
+ end
@@ -9,7 +9,7 @@ module ClaudeHooks
9
9
  end
10
10
 
11
11
  def self.input_fields
12
- %w[tool_name tool_input tool_response]
12
+ %w[tool_name tool_input tool_response tool_use_id]
13
13
  end
14
14
 
15
15
  # === INPUT DATA ACCESS ===
@@ -26,6 +26,10 @@ module ClaudeHooks
26
26
  @input_data['tool_response']
27
27
  end
28
28
 
29
+ def tool_use_id
30
+ @input_data['tool_use_id'] || @input_data['toolUseId']
31
+ end
32
+
29
33
  # === OUTPUT DATA HELPERS ===
30
34
 
31
35
  def block_tool!(reason = '')
@@ -45,5 +49,15 @@ module ClaudeHooks
45
49
  }
46
50
  end
47
51
  alias_method :add_context!, :add_additional_context!
52
+
53
+ def update_tool_output!(value)
54
+ @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
55
+ @output_data['hookSpecificOutput']['updatedToolOutput'] = value
56
+ end
57
+
58
+ def update_mcp_tool_output!(value)
59
+ @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
60
+ @output_data['hookSpecificOutput']['updatedMCPToolOutput'] = value
61
+ end
48
62
  end
49
63
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ class PostToolUseFailure < Base
7
+ def self.hook_type
8
+ 'PostToolUseFailure'
9
+ end
10
+
11
+ def self.input_fields
12
+ %w[tool_name tool_input tool_use_id error]
13
+ end
14
+
15
+ def tool_name
16
+ @input_data['tool_name']
17
+ end
18
+
19
+ def tool_input
20
+ @input_data['tool_input']
21
+ end
22
+
23
+ def tool_use_id
24
+ @input_data['tool_use_id'] || @input_data['toolUseId']
25
+ end
26
+
27
+ def error
28
+ @input_data['error']
29
+ end
30
+
31
+ def is_interrupt
32
+ @input_data.key?('is_interrupt') ? @input_data['is_interrupt'] : @input_data['isInterrupt']
33
+ end
34
+ alias_method :interrupt?, :is_interrupt
35
+
36
+ def duration_ms
37
+ @input_data['duration_ms'] || @input_data['durationMs']
38
+ end
39
+
40
+ def add_additional_context!(context)
41
+ @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
42
+ @output_data['hookSpecificOutput']['additionalContext'] = context
43
+ end
44
+ end
45
+ end
@@ -27,6 +27,13 @@ module ClaudeHooks
27
27
  end
28
28
  end
29
29
 
30
+ # === OUTPUT DATA HELPERS ===
31
+
32
+ def block!(reason = '')
33
+ @output_data['decision'] = 'block'
34
+ @output_data['reason'] = reason
35
+ end
36
+
30
37
  # === UTILITY HELPERS ===
31
38
 
32
39
  def backup_transcript!(backup_file_path)
@@ -9,7 +9,7 @@ module ClaudeHooks
9
9
  end
10
10
 
11
11
  def self.input_fields
12
- %w[tool_name tool_input]
12
+ %w[tool_name tool_input tool_use_id]
13
13
  end
14
14
 
15
15
  # === INPUT DATA ACCESS ===
@@ -22,6 +22,10 @@ module ClaudeHooks
22
22
  @input_data['tool_input']
23
23
  end
24
24
 
25
+ def tool_use_id
26
+ @input_data['tool_use_id'] || @input_data['toolUseId']
27
+ end
28
+
25
29
  # === OUTPUT DATA HELPERS ===
26
30
 
27
31
  def approve_tool!(reason = '')
@@ -47,5 +51,34 @@ module ClaudeHooks
47
51
  'permissionDecisionReason' => reason
48
52
  }
49
53
  end
54
+
55
+ def update_tool_input!(updated_input)
56
+ @output_data['hookSpecificOutput'] ||= {
57
+ 'hookEventName' => hook_event_name,
58
+ 'permissionDecision' => 'allow'
59
+ }
60
+ @output_data['hookSpecificOutput']['updatedInput'] = updated_input
61
+ @output_data['hookSpecificOutput']['permissionDecision'] = 'allow'
62
+ end
63
+
64
+ def defer_permission!
65
+ @output_data['hookSpecificOutput'] = {
66
+ 'hookEventName' => hook_event_name,
67
+ 'permissionDecision' => 'defer'
68
+ }
69
+ end
70
+
71
+ def update_input!(updated_input)
72
+ @output_data['hookSpecificOutput'] ||= {
73
+ 'hookEventName' => hook_event_name,
74
+ 'permissionDecision' => 'allow'
75
+ }
76
+ @output_data['hookSpecificOutput']['updatedInput'] = updated_input
77
+ end
78
+
79
+ def add_additional_context!(context)
80
+ @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
81
+ @output_data['hookSpecificOutput']['additionalContext'] = context
82
+ end
50
83
  end
51
84
  end
@@ -18,6 +18,14 @@ module ClaudeHooks
18
18
  @input_data['source']
19
19
  end
20
20
 
21
+ def model
22
+ @input_data['model']
23
+ end
24
+
25
+ def session_title
26
+ @input_data['session_title'] || @input_data['sessionTitle']
27
+ end
28
+
21
29
  # === OUTPUT DATA HELPERS ===
22
30
 
23
31
  def add_additional_context!(context)
@@ -31,5 +39,25 @@ module ClaudeHooks
31
39
  def empty_additional_context!
32
40
  @output_data['hookSpecificOutput'] = nil
33
41
  end
42
+
43
+ def session_title!(title)
44
+ @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
45
+ @output_data['hookSpecificOutput']['sessionTitle'] = title
46
+ end
47
+
48
+ def initial_user_message!(message)
49
+ @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
50
+ @output_data['hookSpecificOutput']['initialUserMessage'] = message
51
+ end
52
+
53
+ def watch_paths!(paths)
54
+ @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
55
+ @output_data['hookSpecificOutput']['watchPaths'] = Array(paths)
56
+ end
57
+
58
+ def reload_skills!(value = true)
59
+ @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
60
+ @output_data['hookSpecificOutput']['reloadSkills'] = value
61
+ end
34
62
  end
35
63
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ class Setup < Base
7
+ def self.hook_type
8
+ 'Setup'
9
+ end
10
+
11
+ def self.input_fields
12
+ %w[source]
13
+ end
14
+
15
+ def source
16
+ @input_data['source']
17
+ end
18
+
19
+ def add_additional_context!(context)
20
+ @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
21
+ @output_data['hookSpecificOutput']['additionalContext'] = context
22
+ end
23
+ alias_method :add_context!, :add_additional_context!
24
+ end
25
+ end
@@ -26,6 +26,18 @@ module ClaudeHooks
26
26
  @input_data['stop_hook_active']
27
27
  end
28
28
 
29
+ def last_assistant_message
30
+ @input_data['last_assistant_message'] || @input_data['lastAssistantMessage']
31
+ end
32
+
33
+ def background_tasks
34
+ @input_data['background_tasks'] || @input_data['backgroundTasks'] || []
35
+ end
36
+
37
+ def session_crons
38
+ @input_data['session_crons'] || @input_data['sessionCrons'] || []
39
+ end
40
+
29
41
  # === OUTPUT DATA HELPERS ===
30
42
 
31
43
  # Block Claude from stopping (force it to continue)
@@ -40,5 +52,10 @@ module ClaudeHooks
40
52
  @output_data.delete('decision')
41
53
  @output_data.delete('reason')
42
54
  end
55
+
56
+ def add_additional_context!(context)
57
+ @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
58
+ @output_data['hookSpecificOutput']['additionalContext'] = context
59
+ end
43
60
  end
44
61
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ class StopFailure < Base
7
+ def self.hook_type
8
+ 'StopFailure'
9
+ end
10
+
11
+ def self.input_fields
12
+ %w[error]
13
+ end
14
+
15
+ def error
16
+ @input_data['error']
17
+ end
18
+
19
+ def error_details
20
+ @input_data['error_details'] || @input_data['errorDetails']
21
+ end
22
+
23
+ def last_assistant_message
24
+ @input_data['last_assistant_message'] || @input_data['lastAssistantMessage']
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ class SubagentStart < Base
7
+ def self.hook_type
8
+ 'SubagentStart'
9
+ end
10
+
11
+ def self.input_fields
12
+ %w[agent_id agent_type]
13
+ end
14
+
15
+ # agent_id and agent_type readers come from Base (Part A)
16
+
17
+ def add_additional_context!(context)
18
+ @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
19
+ @output_data['hookSpecificOutput']['additionalContext'] = context
20
+ end
21
+ alias_method :add_context!, :add_additional_context!
22
+ end
23
+ end
@@ -7,5 +7,9 @@ module ClaudeHooks
7
7
  def self.hook_type
8
8
  'SubagentStop'
9
9
  end
10
+
11
+ def agent_transcript_path
12
+ @input_data['agent_transcript_path'] || @input_data['agentTranscriptPath']
13
+ end
10
14
  end
11
- end
15
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ class TaskCompleted < Base
7
+ def self.hook_type
8
+ 'TaskCompleted'
9
+ end
10
+
11
+ def self.input_fields
12
+ %w[task_id task_subject]
13
+ end
14
+
15
+ def task_id
16
+ @input_data['task_id'] || @input_data['taskId']
17
+ end
18
+
19
+ def task_subject
20
+ @input_data['task_subject'] || @input_data['taskSubject']
21
+ end
22
+
23
+ def task_description
24
+ @input_data['task_description'] || @input_data['taskDescription']
25
+ end
26
+
27
+ def teammate_name
28
+ @input_data['teammate_name'] || @input_data['teammateName']
29
+ end
30
+
31
+ def team_name
32
+ @input_data['team_name'] || @input_data['teamName']
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ class TaskCreated < Base
7
+ def self.hook_type
8
+ 'TaskCreated'
9
+ end
10
+
11
+ def self.input_fields
12
+ %w[task_id task_subject]
13
+ end
14
+
15
+ def task_id
16
+ @input_data['task_id'] || @input_data['taskId']
17
+ end
18
+
19
+ def task_subject
20
+ @input_data['task_subject'] || @input_data['taskSubject']
21
+ end
22
+
23
+ def task_description
24
+ @input_data['task_description'] || @input_data['taskDescription']
25
+ end
26
+
27
+ def teammate_name
28
+ @input_data['teammate_name'] || @input_data['teammateName']
29
+ end
30
+
31
+ def team_name
32
+ @input_data['team_name'] || @input_data['teamName']
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ class TeammateIdle < Base
7
+ def self.hook_type
8
+ 'TeammateIdle'
9
+ end
10
+
11
+ def self.input_fields
12
+ %w[teammate_name team_name]
13
+ end
14
+
15
+ def teammate_name
16
+ @input_data['teammate_name'] || @input_data['teammateName']
17
+ end
18
+
19
+ def team_name
20
+ @input_data['team_name'] || @input_data['teamName']
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ class UserPromptExpansion < Base
7
+ def self.hook_type
8
+ 'UserPromptExpansion'
9
+ end
10
+
11
+ def self.input_fields
12
+ %w[expansion_type command_name command_args command_source prompt]
13
+ end
14
+
15
+ def expansion_type
16
+ @input_data['expansion_type'] || @input_data['expansionType']
17
+ end
18
+
19
+ def command_name
20
+ @input_data['command_name'] || @input_data['commandName']
21
+ end
22
+
23
+ def command_args
24
+ @input_data['command_args'] || @input_data['commandArgs']
25
+ end
26
+
27
+ def command_source
28
+ @input_data['command_source'] || @input_data['commandSource']
29
+ end
30
+
31
+ def prompt
32
+ @input_data['prompt']
33
+ end
34
+
35
+ def block!(reason = '')
36
+ @output_data['decision'] = 'block'
37
+ @output_data['reason'] = reason
38
+ end
39
+
40
+ def add_additional_context!(context)
41
+ @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
42
+ @output_data['hookSpecificOutput']['additionalContext'] = context
43
+ end
44
+ end
45
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeHooks
4
- VERSION = "1.0.2"
4
+ VERSION = "1.2.0"
5
5
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ class WorktreeCreate < Base
7
+ def self.hook_type
8
+ 'WorktreeCreate'
9
+ end
10
+
11
+ def self.input_fields
12
+ %w[name]
13
+ end
14
+
15
+ def name
16
+ @input_data['name']
17
+ end
18
+
19
+ # Emit the worktree path as bare stdout (last non-empty line).
20
+ # Also sets hookSpecificOutput.worktreePath for programmatic access.
21
+ def worktree_path!(path)
22
+ @output_data['_worktree_path'] = path
23
+ @output_data['hookSpecificOutput'] ||= { 'hookEventName' => hook_event_name }
24
+ @output_data['hookSpecificOutput']['worktreePath'] = path
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module ClaudeHooks
6
+ class WorktreeRemove < Base
7
+ def self.hook_type
8
+ 'WorktreeRemove'
9
+ end
10
+
11
+ def self.input_fields
12
+ %w[worktree_path]
13
+ end
14
+
15
+ def worktree_path
16
+ @input_data['worktree_path'] || @input_data['worktreePath']
17
+ end
18
+ end
19
+ end