aircana 4.0.0.rc2 → 4.0.0.rc3
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.
- checksums.yaml +4 -4
- data/.rspec_status +187 -203
- data/CLAUDE.md +7 -8
- data/README.md +4 -13
- data/lib/aircana/cli/app.rb +0 -31
- data/lib/aircana/cli/commands/generate.rb +19 -14
- data/lib/aircana/cli/commands/init.rb +3 -31
- data/lib/aircana/cli/help_formatter.rb +1 -2
- data/lib/aircana/generators/hooks_generator.rb +2 -2
- data/lib/aircana/templates/hooks/refresh_skills.erb +121 -0
- data/lib/aircana/version.rb +1 -1
- metadata +4 -11
- data/commands/apply-feedback.md +0 -15
- data/commands/ask-expert.md +0 -42
- data/commands/execute.md +0 -13
- data/commands/plan.md +0 -33
- data/commands/record.md +0 -17
- data/commands/review.md +0 -12
- data/hooks/hooks.json +0 -31
- data/lib/aircana/cli/commands/hooks.rb +0 -276
@@ -1,276 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "json"
|
4
|
-
require "tty-prompt"
|
5
|
-
require_relative "../../generators/hooks_generator"
|
6
|
-
require_relative "init"
|
7
|
-
|
8
|
-
module Aircana
|
9
|
-
module CLI
|
10
|
-
module Hooks
|
11
|
-
class << self
|
12
|
-
def list
|
13
|
-
available_hooks = Aircana::Generators::HooksGenerator.all_available_hooks
|
14
|
-
installed_hooks_list = installed_hooks
|
15
|
-
|
16
|
-
if available_hooks.empty?
|
17
|
-
Aircana.human_logger.info "No hooks available."
|
18
|
-
return
|
19
|
-
end
|
20
|
-
|
21
|
-
Aircana.human_logger.info "Available Hooks:"
|
22
|
-
available_hooks.each do |hook_name|
|
23
|
-
status = installed_hooks_list.include?(hook_name) ? "[INSTALLED]" : "[AVAILABLE]"
|
24
|
-
description = hook_description(hook_name)
|
25
|
-
is_default = Aircana::Generators::HooksGenerator.available_default_hooks.include?(hook_name)
|
26
|
-
default_marker = is_default ? " (default)" : ""
|
27
|
-
Aircana.human_logger.info " #{status} #{hook_name} - #{description}#{default_marker}"
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def enable(hook_name)
|
32
|
-
unless Aircana::Generators::HooksGenerator.all_available_hooks.include?(hook_name)
|
33
|
-
Aircana.human_logger.error "Hook '#{hook_name}' is not available."
|
34
|
-
available_hooks_list = Aircana::Generators::HooksGenerator.all_available_hooks.join(", ")
|
35
|
-
Aircana.human_logger.info "Available hooks: #{available_hooks_list}"
|
36
|
-
return
|
37
|
-
end
|
38
|
-
|
39
|
-
# Generate the hook if it doesn't exist
|
40
|
-
Aircana::Generators::HooksGenerator.create_default_hook(hook_name)
|
41
|
-
|
42
|
-
# Install hooks to Claude settings
|
43
|
-
Init.run
|
44
|
-
|
45
|
-
Aircana.human_logger.success "Hook '#{hook_name}' has been enabled."
|
46
|
-
end
|
47
|
-
|
48
|
-
def disable(hook_name)
|
49
|
-
hook_file = File.join(Aircana.configuration.hooks_dir, "#{hook_name}.sh")
|
50
|
-
|
51
|
-
unless File.exist?(hook_file)
|
52
|
-
Aircana.human_logger.warn "Hook '#{hook_name}' is not currently enabled."
|
53
|
-
return
|
54
|
-
end
|
55
|
-
|
56
|
-
File.delete(hook_file)
|
57
|
-
|
58
|
-
# Reinstall remaining hooks to update Claude settings
|
59
|
-
Init.run
|
60
|
-
|
61
|
-
Aircana.human_logger.success "Hook '#{hook_name}' has been disabled."
|
62
|
-
end
|
63
|
-
|
64
|
-
def create
|
65
|
-
prompt = TTY::Prompt.new
|
66
|
-
|
67
|
-
hook_name = prompt.ask("Hook name (lowercase, no spaces):")
|
68
|
-
hook_name = hook_name.strip.downcase.gsub(" ", "_")
|
69
|
-
|
70
|
-
hook_event = prompt.select("Select hook event:", %w[
|
71
|
-
pre_tool_use
|
72
|
-
post_tool_use
|
73
|
-
user_prompt_submit
|
74
|
-
session_start
|
75
|
-
])
|
76
|
-
|
77
|
-
description = prompt.ask("Brief description of what this hook does:")
|
78
|
-
|
79
|
-
# Ensure custom hook names include the event type for proper mapping
|
80
|
-
# unless the name already contains it
|
81
|
-
hook_name = "#{hook_name}_#{hook_event}" unless hook_name.include?(hook_event.gsub("_", ""))
|
82
|
-
|
83
|
-
create_custom_hook(hook_name, hook_event, description)
|
84
|
-
end
|
85
|
-
|
86
|
-
def status
|
87
|
-
settings_file = File.join(Aircana.configuration.claude_code_project_config_path, "settings.local.json")
|
88
|
-
|
89
|
-
unless File.exist?(settings_file)
|
90
|
-
Aircana.human_logger.info "No Claude settings file found at #{settings_file}"
|
91
|
-
return
|
92
|
-
end
|
93
|
-
|
94
|
-
begin
|
95
|
-
settings = JSON.parse(File.read(settings_file))
|
96
|
-
hooks_config = settings["hooks"]
|
97
|
-
|
98
|
-
if hooks_config.nil? || hooks_config.empty?
|
99
|
-
Aircana.human_logger.info "No hooks configured in Claude settings."
|
100
|
-
else
|
101
|
-
Aircana.human_logger.info "Configured hooks in Claude settings:"
|
102
|
-
hooks_config.each do |event, configs|
|
103
|
-
configs = [configs] unless configs.is_a?(Array)
|
104
|
-
configs.each do |config|
|
105
|
-
next unless config["hooks"] && config["hooks"][0] && config["hooks"][0]["command"]
|
106
|
-
|
107
|
-
command_path = config["hooks"][0]["command"]
|
108
|
-
script_name = File.basename(command_path, ".sh")
|
109
|
-
matcher_info = config["matcher"] ? " (matcher: #{config["matcher"]})" : ""
|
110
|
-
Aircana.human_logger.info " #{event}: #{script_name}#{matcher_info}"
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
rescue JSON::ParserError => e
|
115
|
-
Aircana.human_logger.error "Invalid JSON in settings file: #{e.message}"
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
private
|
120
|
-
|
121
|
-
def installed_hooks
|
122
|
-
return [] unless Dir.exist?(Aircana.configuration.hooks_dir)
|
123
|
-
|
124
|
-
Dir.glob("#{Aircana.configuration.hooks_dir}/*.sh").map do |file|
|
125
|
-
File.basename(file, ".sh")
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
def hook_description(hook_name)
|
130
|
-
descriptions = {
|
131
|
-
"pre_tool_use" => "General pre-tool validation hook",
|
132
|
-
"post_tool_use" => "General post-tool processing hook",
|
133
|
-
"user_prompt_submit" => "Add context to user prompts",
|
134
|
-
"session_start" => "Initialize session with project context",
|
135
|
-
"rubocop_pre_commit" => "Run RuboCop before git commits",
|
136
|
-
"rspec_test" => "Run RSpec tests when Ruby files are modified",
|
137
|
-
"bundle_install" => "Run bundle install when Gemfile changes"
|
138
|
-
}
|
139
|
-
descriptions[hook_name] || "Custom hook"
|
140
|
-
end
|
141
|
-
|
142
|
-
def create_custom_hook(hook_name, hook_event, description)
|
143
|
-
template_content = generate_custom_hook_template(hook_event, description)
|
144
|
-
|
145
|
-
hook_file = File.join(Aircana.configuration.hooks_dir, "#{hook_name}.sh")
|
146
|
-
Aircana.create_dir_if_needed(File.dirname(hook_file))
|
147
|
-
|
148
|
-
File.write(hook_file, template_content)
|
149
|
-
File.chmod(0o755, hook_file)
|
150
|
-
|
151
|
-
Aircana.human_logger.success "Custom hook created at #{hook_file}"
|
152
|
-
Aircana.human_logger.info "You may need to customize the hook script for your specific needs."
|
153
|
-
|
154
|
-
# Install hooks to Claude settings
|
155
|
-
Init.run
|
156
|
-
Aircana.human_logger.success "Hook installed to Claude settings"
|
157
|
-
|
158
|
-
# Optionally offer to open in editor
|
159
|
-
prompt = TTY::Prompt.new
|
160
|
-
return unless prompt.yes?("Would you like to edit the hook file now?")
|
161
|
-
|
162
|
-
open_file_in_editor(hook_file)
|
163
|
-
end
|
164
|
-
|
165
|
-
def generate_custom_hook_template(hook_event, description)
|
166
|
-
case hook_event
|
167
|
-
when "pre_tool_use"
|
168
|
-
generate_pre_tool_use_template(description)
|
169
|
-
when "post_tool_use"
|
170
|
-
generate_post_tool_use_template(description)
|
171
|
-
when "user_prompt_submit"
|
172
|
-
generate_user_prompt_submit_template(description)
|
173
|
-
when "session_start"
|
174
|
-
generate_session_start_template(description)
|
175
|
-
else
|
176
|
-
generate_basic_template(hook_event, description)
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
def generate_pre_tool_use_template(description)
|
181
|
-
<<~SCRIPT
|
182
|
-
#!/bin/bash
|
183
|
-
# Custom pre-tool-use hook: #{description}
|
184
|
-
|
185
|
-
TOOL_NAME="$1"
|
186
|
-
TOOL_PARAMS="$2"
|
187
|
-
|
188
|
-
# Add your custom validation logic here
|
189
|
-
# Return exit code 0 to allow, exit code 1 to deny
|
190
|
-
|
191
|
-
echo "Pre-tool validation: $TOOL_NAME"
|
192
|
-
|
193
|
-
# Allow by default
|
194
|
-
exit 0
|
195
|
-
SCRIPT
|
196
|
-
end
|
197
|
-
|
198
|
-
def generate_post_tool_use_template(description)
|
199
|
-
<<~SCRIPT
|
200
|
-
#!/bin/bash
|
201
|
-
# Custom post-tool-use hook: #{description}
|
202
|
-
|
203
|
-
TOOL_NAME="$1"
|
204
|
-
TOOL_PARAMS="$2"
|
205
|
-
TOOL_RESULT="$3"
|
206
|
-
EXIT_CODE="$4"
|
207
|
-
|
208
|
-
# Add your custom post-processing logic here
|
209
|
-
|
210
|
-
echo "Post-tool processing: $TOOL_NAME (exit code: $EXIT_CODE)"
|
211
|
-
|
212
|
-
# Always allow result to proceed
|
213
|
-
exit 0
|
214
|
-
SCRIPT
|
215
|
-
end
|
216
|
-
|
217
|
-
def generate_user_prompt_submit_template(description)
|
218
|
-
<<~SCRIPT
|
219
|
-
#!/bin/bash
|
220
|
-
# Custom user prompt submit hook: #{description}
|
221
|
-
|
222
|
-
USER_PROMPT="$1"
|
223
|
-
|
224
|
-
# Add custom context or modify the prompt here
|
225
|
-
# Output JSON for advanced control or simple exit code
|
226
|
-
|
227
|
-
echo "Processing user prompt"
|
228
|
-
|
229
|
-
# Simple allow - no modifications
|
230
|
-
exit 0
|
231
|
-
SCRIPT
|
232
|
-
end
|
233
|
-
|
234
|
-
def generate_session_start_template(description)
|
235
|
-
<<~SCRIPT
|
236
|
-
#!/bin/bash
|
237
|
-
# Custom session start hook: #{description}
|
238
|
-
|
239
|
-
# Add session initialization logic here
|
240
|
-
|
241
|
-
echo "Session started in $(pwd)"
|
242
|
-
|
243
|
-
# Simple allow
|
244
|
-
exit 0
|
245
|
-
SCRIPT
|
246
|
-
end
|
247
|
-
|
248
|
-
def generate_basic_template(hook_event, description)
|
249
|
-
<<~SCRIPT
|
250
|
-
#!/bin/bash
|
251
|
-
# Custom #{hook_event} hook: #{description}
|
252
|
-
|
253
|
-
# Add your custom hook logic here
|
254
|
-
|
255
|
-
exit 0
|
256
|
-
SCRIPT
|
257
|
-
end
|
258
|
-
|
259
|
-
def open_file_in_editor(file_path)
|
260
|
-
editor = ENV["EDITOR"] || find_available_editor
|
261
|
-
|
262
|
-
if editor
|
263
|
-
Aircana.human_logger.info "Opening #{file_path} in #{editor}..."
|
264
|
-
system("#{editor} '#{file_path}'")
|
265
|
-
else
|
266
|
-
Aircana.human_logger.warn "No editor found. Please edit #{file_path} manually."
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
def find_available_editor
|
271
|
-
%w[code subl atom nano vim vi].find { |cmd| system("which #{cmd} > /dev/null 2>&1") }
|
272
|
-
end
|
273
|
-
end
|
274
|
-
end
|
275
|
-
end
|
276
|
-
end
|