aircana 3.0.0.rc1 → 3.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/.claude-plugin/plugin.json +7 -0
- data/.rspec_status +150 -153
- data/.rubocop.yml +12 -0
- data/CHANGELOG.md +24 -2
- data/CLAUDE.md +50 -19
- data/README.md +132 -55
- data/agents/apply_feedback.md +92 -0
- data/agents/executor.md +85 -0
- data/agents/jira.md +46 -0
- data/agents/planner.md +64 -0
- data/agents/reviewer.md +95 -0
- data/agents/sub-agent-coordinator.md +91 -0
- data/agents/test-agent/manifest.json +15 -0
- data/commands/air-apply-feedback.md +15 -0
- data/commands/air-ask-expert.md +42 -0
- data/commands/air-execute.md +13 -0
- data/commands/air-plan.md +33 -0
- data/commands/air-record.md +17 -0
- data/commands/air-review.md +12 -0
- data/commands/sample-command.md +1 -0
- data/hooks/hooks.json +31 -0
- data/lib/aircana/cli/app.rb +32 -4
- data/lib/aircana/cli/commands/agents.rb +41 -9
- data/lib/aircana/cli/commands/doctor_checks.rb +2 -3
- data/lib/aircana/cli/commands/hooks.rb +4 -4
- data/lib/aircana/cli/commands/init.rb +261 -0
- data/lib/aircana/cli/commands/plugin.rb +157 -0
- data/lib/aircana/cli/help_formatter.rb +1 -1
- data/lib/aircana/configuration.rb +29 -3
- data/lib/aircana/generators/agents_generator.rb +3 -2
- data/lib/aircana/hooks_manifest.rb +189 -0
- data/lib/aircana/plugin_manifest.rb +146 -0
- data/lib/aircana/system_checker.rb +0 -1
- data/lib/aircana/templates/agents/base_agent.erb +2 -2
- data/lib/aircana/templates/hooks/user_prompt_submit.erb +0 -6
- data/lib/aircana/version.rb +1 -1
- data/spec_target_1760205040_181/agents/test-agent/manifest.json +15 -0
- data/spec_target_1760205220_486/agents/test-agent/manifest.json +15 -0
- data/spec_target_1760205379_250/agents/test-agent/manifest.json +15 -0
- data/spec_target_1760205601_652/agents/test-agent/manifest.json +15 -0
- data/spec_target_1760205608_135/agents/test-agent/manifest.json +15 -0
- data/spec_target_1760205654_952/agents/test-agent/manifest.json +15 -0
- metadata +27 -2
- data/lib/aircana/cli/commands/install.rb +0 -169
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "fileutils"
|
5
|
+
|
6
|
+
module Aircana
|
7
|
+
# Manages Claude Code plugin manifest (plugin.json) files
|
8
|
+
class PluginManifest
|
9
|
+
REQUIRED_FIELDS = %w[name version].freeze
|
10
|
+
OPTIONAL_FIELDS = %w[description author homepage repository license keywords].freeze
|
11
|
+
ALL_FIELDS = (REQUIRED_FIELDS + OPTIONAL_FIELDS).freeze
|
12
|
+
|
13
|
+
attr_reader :plugin_root
|
14
|
+
|
15
|
+
def initialize(plugin_root)
|
16
|
+
@plugin_root = plugin_root
|
17
|
+
end
|
18
|
+
|
19
|
+
# Creates a new plugin manifest with the given attributes
|
20
|
+
def create(attributes = {})
|
21
|
+
validate_required_fields!(attributes)
|
22
|
+
|
23
|
+
manifest_data = build_manifest_data(attributes)
|
24
|
+
write_manifest(manifest_data)
|
25
|
+
|
26
|
+
manifest_path
|
27
|
+
end
|
28
|
+
|
29
|
+
# Reads the existing plugin manifest
|
30
|
+
def read
|
31
|
+
return nil unless exists?
|
32
|
+
|
33
|
+
JSON.parse(File.read(manifest_path))
|
34
|
+
rescue JSON::ParserError => e
|
35
|
+
raise Aircana::Error, "Invalid JSON in plugin manifest: #{e.message}"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Updates the plugin manifest with new values
|
39
|
+
def update(attributes = {})
|
40
|
+
current_data = read || {}
|
41
|
+
updated_data = current_data.merge(attributes.transform_keys(&:to_s))
|
42
|
+
|
43
|
+
validate_required_fields!(updated_data)
|
44
|
+
write_manifest(updated_data)
|
45
|
+
|
46
|
+
manifest_path
|
47
|
+
end
|
48
|
+
|
49
|
+
# Bumps the version number (major, minor, or patch)
|
50
|
+
def bump_version(type = :patch)
|
51
|
+
current_data = read
|
52
|
+
raise Aircana::Error, "No plugin manifest found at #{manifest_path}" unless current_data
|
53
|
+
|
54
|
+
current_version = current_data["version"]
|
55
|
+
new_version = bump_semantic_version(current_version, type)
|
56
|
+
|
57
|
+
update("version" => new_version)
|
58
|
+
new_version
|
59
|
+
end
|
60
|
+
|
61
|
+
# Checks if the plugin manifest exists
|
62
|
+
def exists?
|
63
|
+
File.exist?(manifest_path)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the path to the plugin manifest
|
67
|
+
def manifest_path
|
68
|
+
File.join(plugin_root, ".claude-plugin", "plugin.json")
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the directory containing the manifest
|
72
|
+
def manifest_dir
|
73
|
+
File.join(plugin_root, ".claude-plugin")
|
74
|
+
end
|
75
|
+
|
76
|
+
# Validates the current manifest structure
|
77
|
+
def validate!
|
78
|
+
data = read
|
79
|
+
raise Aircana::Error, "No plugin manifest found" unless data
|
80
|
+
|
81
|
+
validate_required_fields!(data)
|
82
|
+
validate_version_format!(data["version"])
|
83
|
+
|
84
|
+
true
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def build_manifest_data(attributes)
|
90
|
+
data = {
|
91
|
+
"name" => attributes[:name] || attributes["name"],
|
92
|
+
"version" => attributes[:version] || attributes["version"] || "0.1.0"
|
93
|
+
}
|
94
|
+
|
95
|
+
# Add optional fields if provided
|
96
|
+
OPTIONAL_FIELDS.each do |field|
|
97
|
+
value = attributes[field.to_sym] || attributes[field]
|
98
|
+
data[field] = value if value
|
99
|
+
end
|
100
|
+
|
101
|
+
data
|
102
|
+
end
|
103
|
+
|
104
|
+
def write_manifest(data)
|
105
|
+
FileUtils.mkdir_p(manifest_dir)
|
106
|
+
File.write(manifest_path, JSON.pretty_generate(data))
|
107
|
+
end
|
108
|
+
|
109
|
+
def validate_required_fields!(data)
|
110
|
+
REQUIRED_FIELDS.each do |field|
|
111
|
+
unless data[field] || data[field.to_sym]
|
112
|
+
raise Aircana::Error, "Plugin manifest missing required field: #{field}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def validate_version_format!(version)
|
118
|
+
return if version.match?(/^\d+\.\d+\.\d+/)
|
119
|
+
|
120
|
+
raise Aircana::Error, "Invalid version format: #{version}. Must be semantic versioning (e.g., 1.0.0)"
|
121
|
+
end
|
122
|
+
|
123
|
+
def bump_semantic_version(version, type)
|
124
|
+
parts = version.split(".").map(&:to_i)
|
125
|
+
raise Aircana::Error, "Invalid version format: #{version}" if parts.size != 3
|
126
|
+
|
127
|
+
case type.to_sym
|
128
|
+
when :major
|
129
|
+
[parts[0] + 1, 0, 0].join(".")
|
130
|
+
when :minor
|
131
|
+
[parts[0], parts[1] + 1, 0].join(".")
|
132
|
+
when :patch
|
133
|
+
[parts[0], parts[1], parts[2] + 1].join(".")
|
134
|
+
else
|
135
|
+
raise Aircana::Error, "Invalid version bump type: #{type}. Must be major, minor, or patch"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class << self
|
140
|
+
# Creates a default plugin name from a directory path
|
141
|
+
def default_plugin_name(directory)
|
142
|
+
File.basename(directory).downcase.gsub(/[^a-z0-9]+/, "-")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -98,7 +98,6 @@ module Aircana
|
|
98
98
|
def check_configuration_directories
|
99
99
|
{
|
100
100
|
global: File.expand_path("~/.aircana"),
|
101
|
-
project: File.join(Dir.pwd, ".aircana"),
|
102
101
|
claude_global: File.expand_path("~/.claude"),
|
103
102
|
claude_project: File.join(Dir.pwd, ".claude")
|
104
103
|
}.transform_values { |path| Dir.exist?(path) }
|
@@ -5,7 +5,7 @@ model: <%= model %>
|
|
5
5
|
color: <%= color %>
|
6
6
|
---
|
7
7
|
|
8
|
-
<%= helpers.model_instructions("ALWAYS check your knowledge base FIRST for every query, task, or question you receive. Use `ls
|
8
|
+
<%= helpers.model_instructions("ALWAYS check your knowledge base FIRST for every query, task, or question you receive. Use `ls ${CLAUDE_PLUGIN_ROOT}/agents/#{agent_name}/knowledge/*.md` to list files from your knowledge base.
|
9
9
|
|
10
10
|
MANDATORY WORKFLOW:
|
11
11
|
1. BEFORE responding to ANY request - search and read relevant files in your knowledge base
|
@@ -18,7 +18,7 @@ Your knowledge base contains domain-specific information that takes priority ove
|
|
18
18
|
|
19
19
|
## Knowledge Base Integration
|
20
20
|
|
21
|
-
Your specialized knowledge is in the
|
21
|
+
Your specialized knowledge is in the plugin directory. Use `ls ${CLAUDE_PLUGIN_ROOT}/agents/<%= agent_name %>/knowledge/*.md` to list available files, then read them with the Read tool.
|
22
22
|
|
23
23
|
This knowledge base contains:
|
24
24
|
- Domain-specific documentation from Confluence
|
@@ -22,12 +22,6 @@ if [ -f "Gemfile" ]; then
|
|
22
22
|
fi
|
23
23
|
fi
|
24
24
|
|
25
|
-
# Check for relevant files context
|
26
|
-
if [ -d ".aircana/relevant_files" ] && [ "$(ls -A .aircana/relevant_files 2>/dev/null)" ]; then
|
27
|
-
RELEVANT_COUNT=$(ls .aircana/relevant_files | wc -l)
|
28
|
-
CONTEXT_ADDITIONS="${CONTEXT_ADDITIONS}\n\nRelevant Files: $RELEVANT_COUNT files currently in context."
|
29
|
-
fi
|
30
|
-
|
31
25
|
# Output JSON response with additional context
|
32
26
|
if [ -n "$CONTEXT_ADDITIONS" ]; then
|
33
27
|
# Escape context for JSON
|
data/lib/aircana/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aircana
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.0.
|
4
|
+
version: 3.0.0.rc3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Weston Dransfield
|
@@ -102,6 +102,7 @@ executables:
|
|
102
102
|
extensions: []
|
103
103
|
extra_rdoc_files: []
|
104
104
|
files:
|
105
|
+
- ".claude-plugin/plugin.json"
|
105
106
|
- ".devcontainer/devcontainer.json"
|
106
107
|
- ".dockerignore"
|
107
108
|
- ".rspec_status"
|
@@ -113,8 +114,23 @@ files:
|
|
113
114
|
- README.md
|
114
115
|
- Rakefile
|
115
116
|
- SECURITY.md
|
117
|
+
- agents/apply_feedback.md
|
118
|
+
- agents/executor.md
|
119
|
+
- agents/jira.md
|
120
|
+
- agents/planner.md
|
121
|
+
- agents/reviewer.md
|
122
|
+
- agents/sub-agent-coordinator.md
|
123
|
+
- agents/test-agent/manifest.json
|
124
|
+
- commands/air-apply-feedback.md
|
125
|
+
- commands/air-ask-expert.md
|
126
|
+
- commands/air-execute.md
|
127
|
+
- commands/air-plan.md
|
128
|
+
- commands/air-record.md
|
129
|
+
- commands/air-review.md
|
130
|
+
- commands/sample-command.md
|
116
131
|
- compose.yml
|
117
132
|
- exe/aircana
|
133
|
+
- hooks/hooks.json
|
118
134
|
- lib/aircana.rb
|
119
135
|
- lib/aircana/cli.rb
|
120
136
|
- lib/aircana/cli/app.rb
|
@@ -125,7 +141,8 @@ files:
|
|
125
141
|
- lib/aircana/cli/commands/dump_context.rb
|
126
142
|
- lib/aircana/cli/commands/generate.rb
|
127
143
|
- lib/aircana/cli/commands/hooks.rb
|
128
|
-
- lib/aircana/cli/commands/
|
144
|
+
- lib/aircana/cli/commands/init.rb
|
145
|
+
- lib/aircana/cli/commands/plugin.rb
|
129
146
|
- lib/aircana/cli/help_formatter.rb
|
130
147
|
- lib/aircana/cli/shell_command.rb
|
131
148
|
- lib/aircana/cli/subcommand.rb
|
@@ -150,9 +167,11 @@ files:
|
|
150
167
|
- lib/aircana/generators/plan_command_generator.rb
|
151
168
|
- lib/aircana/generators/record_command_generator.rb
|
152
169
|
- lib/aircana/generators/review_command_generator.rb
|
170
|
+
- lib/aircana/hooks_manifest.rb
|
153
171
|
- lib/aircana/human_logger.rb
|
154
172
|
- lib/aircana/initializers.rb
|
155
173
|
- lib/aircana/llm/claude_client.rb
|
174
|
+
- lib/aircana/plugin_manifest.rb
|
156
175
|
- lib/aircana/progress_tracker.rb
|
157
176
|
- lib/aircana/system_checker.rb
|
158
177
|
- lib/aircana/templates/agents/base_agent.erb
|
@@ -178,6 +197,12 @@ files:
|
|
178
197
|
- lib/aircana/templates/hooks/user_prompt_submit.erb
|
179
198
|
- lib/aircana/version.rb
|
180
199
|
- sig/aircana.rbs
|
200
|
+
- spec_target_1760205040_181/agents/test-agent/manifest.json
|
201
|
+
- spec_target_1760205220_486/agents/test-agent/manifest.json
|
202
|
+
- spec_target_1760205379_250/agents/test-agent/manifest.json
|
203
|
+
- spec_target_1760205601_652/agents/test-agent/manifest.json
|
204
|
+
- spec_target_1760205608_135/agents/test-agent/manifest.json
|
205
|
+
- spec_target_1760205654_952/agents/test-agent/manifest.json
|
181
206
|
homepage: https://github.com/westonkd/aircana
|
182
207
|
licenses:
|
183
208
|
- MIT
|
@@ -1,169 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "json"
|
4
|
-
require_relative "generate"
|
5
|
-
|
6
|
-
module Aircana
|
7
|
-
module CLI
|
8
|
-
module Install
|
9
|
-
class << self
|
10
|
-
def run
|
11
|
-
generate_files
|
12
|
-
install_commands_to_claude
|
13
|
-
install_hooks_to_claude
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
def generate_files
|
19
|
-
Aircana.human_logger.info("Generating files before installation...")
|
20
|
-
Generate.run
|
21
|
-
end
|
22
|
-
|
23
|
-
def install_commands_to_claude
|
24
|
-
claude_commands_dir = File.join(Aircana.configuration.claude_code_project_config_path, "commands")
|
25
|
-
Aircana.create_dir_if_needed(claude_commands_dir)
|
26
|
-
|
27
|
-
copy_command_files(claude_commands_dir)
|
28
|
-
install_agents_to_claude
|
29
|
-
end
|
30
|
-
|
31
|
-
def copy_command_files(destination_dir)
|
32
|
-
Dir.glob("#{Aircana.configuration.output_dir}/commands/*").each do |file|
|
33
|
-
Aircana.human_logger.success("Installing #{file} to #{destination_dir}")
|
34
|
-
FileUtils.cp(file, destination_dir)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def install_agents_to_claude
|
39
|
-
claude_agents_dir = File.join(Aircana.configuration.claude_code_project_config_path, "agents")
|
40
|
-
Aircana.create_dir_if_needed(claude_agents_dir)
|
41
|
-
|
42
|
-
copy_agent_files(claude_agents_dir)
|
43
|
-
end
|
44
|
-
|
45
|
-
def copy_agent_files(destination_dir)
|
46
|
-
agent_files_pattern = File.join(Aircana.configuration.output_dir, "agents", "*.md")
|
47
|
-
Dir.glob(agent_files_pattern).each do |file|
|
48
|
-
agent_name = File.basename(file, ".md")
|
49
|
-
next unless default_agent?(agent_name)
|
50
|
-
|
51
|
-
destination_file = File.join(destination_dir, File.basename(file))
|
52
|
-
# Skip copying if source and destination are the same
|
53
|
-
next if File.expand_path(file) == File.expand_path(destination_file)
|
54
|
-
|
55
|
-
Aircana.human_logger.success("Installing default agent #{file} to #{destination_dir}")
|
56
|
-
FileUtils.cp(file, destination_dir)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def default_agent?(agent_name)
|
61
|
-
require_relative "../../generators/agents_generator"
|
62
|
-
Aircana::Generators::AgentsGenerator.available_default_agents.include?(agent_name)
|
63
|
-
end
|
64
|
-
|
65
|
-
def install_hooks_to_claude
|
66
|
-
return unless Dir.exist?(Aircana.configuration.hooks_dir)
|
67
|
-
|
68
|
-
settings_file = File.join(Aircana.configuration.claude_code_project_config_path, "settings.local.json")
|
69
|
-
install_hooks_to_settings(settings_file)
|
70
|
-
end
|
71
|
-
|
72
|
-
def install_hooks_to_settings(settings_file)
|
73
|
-
settings = load_settings(settings_file)
|
74
|
-
hook_configs = build_hook_configs
|
75
|
-
|
76
|
-
return if hook_configs.empty?
|
77
|
-
|
78
|
-
settings["hooks"] = hook_configs
|
79
|
-
save_settings(settings_file, settings)
|
80
|
-
|
81
|
-
Aircana.human_logger.success("Installed hooks to #{settings_file}")
|
82
|
-
end
|
83
|
-
|
84
|
-
def load_settings(settings_file)
|
85
|
-
if File.exist?(settings_file)
|
86
|
-
JSON.parse(File.read(settings_file))
|
87
|
-
else
|
88
|
-
Aircana.create_dir_if_needed(File.dirname(settings_file))
|
89
|
-
{}
|
90
|
-
end
|
91
|
-
rescue JSON::ParserError
|
92
|
-
Aircana.human_logger.warn("Invalid JSON in #{settings_file}, creating new settings")
|
93
|
-
{}
|
94
|
-
end
|
95
|
-
|
96
|
-
def save_settings(settings_file, settings)
|
97
|
-
File.write(settings_file, JSON.pretty_generate(settings))
|
98
|
-
end
|
99
|
-
|
100
|
-
def build_hook_configs
|
101
|
-
hooks = {}
|
102
|
-
|
103
|
-
# Map hook files to Claude Code hook events and their properties
|
104
|
-
hook_mappings = {
|
105
|
-
"pre_tool_use" => { event: "PreToolUse", matcher: nil },
|
106
|
-
"post_tool_use" => { event: "PostToolUse", matcher: nil },
|
107
|
-
"user_prompt_submit" => { event: "UserPromptSubmit", matcher: nil },
|
108
|
-
"session_start" => { event: "SessionStart", matcher: nil },
|
109
|
-
"notification_sqs" => { event: "Notification", matcher: nil },
|
110
|
-
"rubocop_pre_commit" => { event: "PreToolUse", matcher: "Bash" },
|
111
|
-
"rspec_test" => { event: "PostToolUse", matcher: "Bash" },
|
112
|
-
"bundle_install" => { event: "PostToolUse", matcher: "Bash" }
|
113
|
-
}
|
114
|
-
|
115
|
-
Dir.glob("#{Aircana.configuration.hooks_dir}/*.sh").each do |hook_file|
|
116
|
-
hook_name = File.basename(hook_file, ".sh")
|
117
|
-
|
118
|
-
# Determine mapping for this hook
|
119
|
-
mapping = if hook_mappings.key?(hook_name)
|
120
|
-
hook_mappings[hook_name]
|
121
|
-
else
|
122
|
-
# For custom hooks, try to infer the event type from the filename
|
123
|
-
infer_hook_mapping(hook_name)
|
124
|
-
end
|
125
|
-
|
126
|
-
next unless mapping
|
127
|
-
|
128
|
-
event_key = mapping[:event]
|
129
|
-
|
130
|
-
# Create relative path from project root
|
131
|
-
relative_path = File.join(".aircana", "hooks", "#{hook_name}.sh")
|
132
|
-
|
133
|
-
hook_entry = {
|
134
|
-
"hooks" => [
|
135
|
-
{
|
136
|
-
"type" => "command",
|
137
|
-
"command" => relative_path
|
138
|
-
}
|
139
|
-
]
|
140
|
-
}
|
141
|
-
|
142
|
-
# Add matcher if specified
|
143
|
-
hook_entry["matcher"] = mapping[:matcher] if mapping[:matcher]
|
144
|
-
|
145
|
-
hooks[event_key] ||= []
|
146
|
-
hooks[event_key] << hook_entry
|
147
|
-
end
|
148
|
-
|
149
|
-
hooks
|
150
|
-
end
|
151
|
-
|
152
|
-
def infer_hook_mapping(hook_name)
|
153
|
-
# Try to infer the event type from common patterns in the hook name
|
154
|
-
case hook_name
|
155
|
-
when /pre_tool_use|pre_tool|before_tool/i
|
156
|
-
{ event: "PreToolUse", matcher: nil }
|
157
|
-
when /user_prompt|prompt_submit|before_prompt/i
|
158
|
-
{ event: "UserPromptSubmit", matcher: nil }
|
159
|
-
when /session_start|session_init|startup/i
|
160
|
-
{ event: "SessionStart", matcher: nil }
|
161
|
-
else
|
162
|
-
# Default to PostToolUse for unknown custom hooks and post_tool patterns
|
163
|
-
{ event: "PostToolUse", matcher: nil }
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
169
|
-
end
|