aircana 2.0.0 → 3.0.0.rc2
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 +184 -187
- data/.rubocop.yml +12 -0
- data/CHANGELOG.md +38 -0
- data/CLAUDE.md +51 -20
- data/README.md +132 -63
- 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 +27 -30
- data/lib/aircana/cli/commands/agents.rb +41 -9
- data/lib/aircana/cli/commands/doctor_checks.rb +2 -3
- data/lib/aircana/cli/commands/generate.rb +0 -11
- data/lib/aircana/cli/commands/hooks.rb +4 -4
- data/lib/aircana/cli/commands/init.rb +266 -0
- data/lib/aircana/cli/commands/plugin.rb +157 -0
- data/lib/aircana/cli/help_formatter.rb +2 -3
- data/lib/aircana/configuration.rb +29 -3
- data/lib/aircana/contexts/manifest.rb +1 -8
- 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 -11
- data/lib/aircana/templates/agents/base_agent.erb +2 -2
- data/lib/aircana/templates/hooks/session_start.erb +3 -118
- 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 +29 -7
- data/lib/aircana/cli/commands/install.rb +0 -179
- data/lib/aircana/cli/commands/project.rb +0 -156
- data/lib/aircana/generators/project_config_generator.rb +0 -54
- data/lib/aircana/symlink_manager.rb +0 -158
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "fileutils"
|
5
|
+
|
6
|
+
module Aircana
|
7
|
+
# Manages Claude Code plugin hooks manifest (hooks/hooks.json) files
|
8
|
+
class HooksManifest
|
9
|
+
VALID_EVENTS = %w[PreToolUse PostToolUse UserPromptSubmit SessionStart Notification].freeze
|
10
|
+
VALID_HOOK_TYPES = %w[command validation notification].freeze
|
11
|
+
|
12
|
+
attr_reader :plugin_root
|
13
|
+
|
14
|
+
def initialize(plugin_root)
|
15
|
+
@plugin_root = plugin_root
|
16
|
+
end
|
17
|
+
|
18
|
+
# Creates a new hooks manifest with the given hooks configuration
|
19
|
+
def create(hooks_config = {})
|
20
|
+
validate_hooks_config!(hooks_config)
|
21
|
+
write_manifest(hooks_config)
|
22
|
+
|
23
|
+
manifest_path
|
24
|
+
end
|
25
|
+
|
26
|
+
# Reads the existing hooks manifest
|
27
|
+
def read
|
28
|
+
return nil unless exists?
|
29
|
+
|
30
|
+
JSON.parse(File.read(manifest_path))
|
31
|
+
rescue JSON::ParserError => e
|
32
|
+
raise Aircana::Error, "Invalid JSON in hooks manifest: #{e.message}"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Updates the hooks manifest with new values
|
36
|
+
def update(hooks_config = {})
|
37
|
+
current_data = read || {}
|
38
|
+
updated_data = deep_merge(current_data, hooks_config)
|
39
|
+
|
40
|
+
validate_hooks_config!(updated_data)
|
41
|
+
write_manifest(updated_data)
|
42
|
+
|
43
|
+
manifest_path
|
44
|
+
end
|
45
|
+
|
46
|
+
# Adds a hook to the manifest
|
47
|
+
def add_hook(event:, hook_entry:, matcher: nil)
|
48
|
+
validate_event!(event)
|
49
|
+
validate_hook_entry!(hook_entry)
|
50
|
+
|
51
|
+
current_data = read || {}
|
52
|
+
current_data[event] ||= []
|
53
|
+
|
54
|
+
hook_config = build_hook_config(hook_entry, matcher)
|
55
|
+
current_data[event] << hook_config
|
56
|
+
|
57
|
+
write_manifest(current_data)
|
58
|
+
manifest_path
|
59
|
+
end
|
60
|
+
|
61
|
+
# Removes a hook from the manifest
|
62
|
+
def remove_hook(event:, command:)
|
63
|
+
current_data = read
|
64
|
+
return manifest_path unless current_data && current_data[event]
|
65
|
+
|
66
|
+
current_data[event].reject! do |hook_group|
|
67
|
+
hook_group["hooks"]&.any? { |h| h["command"] == command }
|
68
|
+
end
|
69
|
+
|
70
|
+
current_data.delete(event) if current_data[event].empty?
|
71
|
+
|
72
|
+
write_manifest(current_data)
|
73
|
+
manifest_path
|
74
|
+
end
|
75
|
+
|
76
|
+
# Checks if the hooks manifest exists
|
77
|
+
def exists?
|
78
|
+
File.exist?(manifest_path)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns the path to the hooks manifest
|
82
|
+
def manifest_path
|
83
|
+
File.join(plugin_root, "hooks", "hooks.json")
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns the hooks directory
|
87
|
+
def hooks_dir
|
88
|
+
File.join(plugin_root, "hooks")
|
89
|
+
end
|
90
|
+
|
91
|
+
# Validates the current manifest structure
|
92
|
+
def validate!
|
93
|
+
data = read
|
94
|
+
return true unless data # Empty manifest is valid
|
95
|
+
|
96
|
+
validate_hooks_config!(data)
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
100
|
+
# Converts old settings.local.json hook format to hooks.json format
|
101
|
+
def self.from_settings_format(settings_hooks)
|
102
|
+
hooks_config = {}
|
103
|
+
|
104
|
+
settings_hooks.each do |event, hook_groups|
|
105
|
+
hooks_config[event] = hook_groups.map do |group|
|
106
|
+
{
|
107
|
+
"hooks" => group["hooks"],
|
108
|
+
"matcher" => group["matcher"]
|
109
|
+
}.compact
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
hooks_config
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def build_hook_config(hook_entry, matcher)
|
119
|
+
config = {
|
120
|
+
"hooks" => [hook_entry]
|
121
|
+
}
|
122
|
+
config["matcher"] = matcher if matcher
|
123
|
+
config
|
124
|
+
end
|
125
|
+
|
126
|
+
def write_manifest(data)
|
127
|
+
FileUtils.mkdir_p(hooks_dir)
|
128
|
+
File.write(manifest_path, JSON.pretty_generate(data))
|
129
|
+
end
|
130
|
+
|
131
|
+
def validate_hooks_config!(config)
|
132
|
+
return if config.nil? || config.empty?
|
133
|
+
|
134
|
+
config.each do |event, hook_groups|
|
135
|
+
validate_event!(event)
|
136
|
+
|
137
|
+
raise Aircana::Error, "Hook configuration for #{event} must be an array" unless hook_groups.is_a?(Array)
|
138
|
+
|
139
|
+
hook_groups.each do |group|
|
140
|
+
validate_hook_group!(group)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def validate_event!(event)
|
146
|
+
return if VALID_EVENTS.include?(event)
|
147
|
+
|
148
|
+
raise Aircana::Error, "Invalid hook event: #{event}. Must be one of: #{VALID_EVENTS.join(", ")}"
|
149
|
+
end
|
150
|
+
|
151
|
+
def validate_hook_group!(group)
|
152
|
+
raise Aircana::Error, "Hook group must be a hash with 'hooks' array" unless group.is_a?(Hash) && group["hooks"]
|
153
|
+
|
154
|
+
raise Aircana::Error, "Hook group 'hooks' must be an array" unless group["hooks"].is_a?(Array)
|
155
|
+
|
156
|
+
group["hooks"].each do |hook|
|
157
|
+
validate_hook_entry!(hook)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def validate_hook_entry!(hook)
|
162
|
+
raise Aircana::Error, "Hook entry must be a hash" unless hook.is_a?(Hash)
|
163
|
+
|
164
|
+
unless hook["type"] && VALID_HOOK_TYPES.include?(hook["type"])
|
165
|
+
raise Aircana::Error, "Hook must have a valid type: #{VALID_HOOK_TYPES.join(", ")}"
|
166
|
+
end
|
167
|
+
|
168
|
+
return if hook["command"]
|
169
|
+
|
170
|
+
raise Aircana::Error, "Hook must have a command"
|
171
|
+
end
|
172
|
+
|
173
|
+
def deep_merge(hash1, hash2)
|
174
|
+
result = hash1.dup
|
175
|
+
|
176
|
+
hash2.each do |key, value|
|
177
|
+
result[key] = if result[key].is_a?(Hash) && value.is_a?(Hash)
|
178
|
+
deep_merge(result[key], value)
|
179
|
+
elsif result[key].is_a?(Array) && value.is_a?(Array)
|
180
|
+
result[key] + value
|
181
|
+
else
|
182
|
+
value
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
result
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -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
|
@@ -23,16 +23,6 @@ module Aircana
|
|
23
23
|
"Arch" => "pacman -S git",
|
24
24
|
"Other" => "https://git-scm.com/downloads"
|
25
25
|
}
|
26
|
-
},
|
27
|
-
"jq" => {
|
28
|
-
purpose: "JSON parsing for multi-root configuration",
|
29
|
-
install: {
|
30
|
-
"macOS" => "brew install jq",
|
31
|
-
"Ubuntu/Debian" => "apt install jq",
|
32
|
-
"Fedora/CentOS" => "dnf install jq",
|
33
|
-
"Arch" => "pacman -S jq",
|
34
|
-
"Other" => "https://jqlang.github.io/jq/download/"
|
35
|
-
}
|
36
26
|
}
|
37
27
|
}.freeze
|
38
28
|
|
@@ -108,7 +98,6 @@ module Aircana
|
|
108
98
|
def check_configuration_directories
|
109
99
|
{
|
110
100
|
global: File.expand_path("~/.aircana"),
|
111
|
-
project: File.join(Dir.pwd, ".aircana"),
|
112
101
|
claude_global: File.expand_path("~/.claude"),
|
113
102
|
claude_project: File.join(Dir.pwd, ".claude")
|
114
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
|
@@ -1,127 +1,12 @@
|
|
1
1
|
#!/bin/bash
|
2
|
-
#
|
2
|
+
# Session start hook for Aircana
|
3
3
|
# This hook runs when a new Claude Code session starts
|
4
4
|
|
5
|
-
PROJECT_JSON=".aircana/project.json"
|
6
|
-
CLAUDE_AGENTS_DIR=".claude/agents"
|
7
|
-
AIRCANA_AGENTS_DIR=".aircana/agents"
|
8
|
-
|
9
5
|
# Create log directory if it doesn't exist
|
10
6
|
mkdir -p ~/.aircana
|
11
7
|
|
12
8
|
# Log session start
|
13
9
|
echo "$(date): New Claude Code session started in $(pwd)" >> ~/.aircana/hooks.log
|
14
10
|
|
15
|
-
#
|
16
|
-
|
17
|
-
echo "$(date): Warning - jq not found. Multi-root support disabled." >> ~/.aircana/hooks.log
|
18
|
-
echo "{}"
|
19
|
-
exit 0
|
20
|
-
fi
|
21
|
-
|
22
|
-
# Check if project.json exists
|
23
|
-
if [ ! -f "$PROJECT_JSON" ]; then
|
24
|
-
echo "$(date): No project.json found, skipping multi-root setup" >> ~/.aircana/hooks.log
|
25
|
-
echo "{}"
|
26
|
-
exit 0
|
27
|
-
fi
|
28
|
-
|
29
|
-
echo "$(date): Processing multi-root configuration from $PROJECT_JSON" >> ~/.aircana/hooks.log
|
30
|
-
|
31
|
-
# Ensure directories exist
|
32
|
-
mkdir -p "$CLAUDE_AGENTS_DIR" 2>/dev/null
|
33
|
-
mkdir -p "$AIRCANA_AGENTS_DIR" 2>/dev/null
|
34
|
-
|
35
|
-
# Clean up existing symlinks (only remove symlinks, not real files)
|
36
|
-
find "$CLAUDE_AGENTS_DIR" -type l -delete 2>/dev/null
|
37
|
-
find "$AIRCANA_AGENTS_DIR" -type l -delete 2>/dev/null
|
38
|
-
|
39
|
-
# Parse folders from project.json
|
40
|
-
FOLDERS=$(jq -r '.folders[]?.path // empty' "$PROJECT_JSON" 2>/dev/null)
|
41
|
-
|
42
|
-
if [ -z "$FOLDERS" ]; then
|
43
|
-
echo "$(date): No folders configured in project.json" >> ~/.aircana/hooks.log
|
44
|
-
echo "{}"
|
45
|
-
exit 0
|
46
|
-
fi
|
47
|
-
|
48
|
-
# Track what we've linked for reporting
|
49
|
-
LINKED_AGENTS=0
|
50
|
-
LINKED_KNOWLEDGE=0
|
51
|
-
|
52
|
-
# Create symlinks for each configured folder
|
53
|
-
for folder in $FOLDERS; do
|
54
|
-
# Validate folder exists
|
55
|
-
if [ ! -d "$folder" ]; then
|
56
|
-
echo "$(date): Warning - folder '$folder' not found, skipping" >> ~/.aircana/hooks.log
|
57
|
-
continue
|
58
|
-
fi
|
59
|
-
|
60
|
-
echo "$(date): Processing folder: $folder" >> ~/.aircana/hooks.log
|
61
|
-
|
62
|
-
# Get folder name for prefix (replace slashes with underscores for nested paths)
|
63
|
-
PREFIX=$(echo "$folder" | tr '/' '_')
|
64
|
-
|
65
|
-
# Link agents from sub-folder .claude/agents
|
66
|
-
if [ -d "$folder/.claude/agents" ]; then
|
67
|
-
for agent_file in "$folder/.claude/agents"/*.md; do
|
68
|
-
if [ ! -f "$agent_file" ]; then
|
69
|
-
continue
|
70
|
-
fi
|
71
|
-
|
72
|
-
AGENT_NAME=$(basename "$agent_file" .md)
|
73
|
-
LINK_NAME="${PREFIX}_${AGENT_NAME}.md"
|
74
|
-
TARGET_PATH="$CLAUDE_AGENTS_DIR/$LINK_NAME"
|
75
|
-
|
76
|
-
# Create relative path from .claude/agents to the agent file
|
77
|
-
RELATIVE_PATH=$(realpath --relative-to="$CLAUDE_AGENTS_DIR" "$agent_file" 2>/dev/null)
|
78
|
-
|
79
|
-
if [ -n "$RELATIVE_PATH" ]; then
|
80
|
-
ln -sf "$RELATIVE_PATH" "$TARGET_PATH"
|
81
|
-
echo "$(date): Linked agent: $LINK_NAME -> $RELATIVE_PATH" >> ~/.aircana/hooks.log
|
82
|
-
((LINKED_AGENTS++))
|
83
|
-
fi
|
84
|
-
done
|
85
|
-
fi
|
86
|
-
|
87
|
-
# Link knowledge from sub-folder .aircana/agents
|
88
|
-
if [ -d "$folder/.aircana/agents" ]; then
|
89
|
-
for agent_dir in "$folder/.aircana/agents"/*; do
|
90
|
-
if [ ! -d "$agent_dir" ]; then
|
91
|
-
continue
|
92
|
-
fi
|
93
|
-
|
94
|
-
AGENT_NAME=$(basename "$agent_dir")
|
95
|
-
LINK_NAME="${PREFIX}_${AGENT_NAME}"
|
96
|
-
TARGET_PATH="$AIRCANA_AGENTS_DIR/$LINK_NAME"
|
97
|
-
|
98
|
-
# Create relative path from .aircana/agents to the knowledge directory
|
99
|
-
RELATIVE_PATH=$(realpath --relative-to="$AIRCANA_AGENTS_DIR" "$agent_dir" 2>/dev/null)
|
100
|
-
|
101
|
-
if [ -n "$RELATIVE_PATH" ]; then
|
102
|
-
ln -sf "$RELATIVE_PATH" "$TARGET_PATH"
|
103
|
-
echo "$(date): Linked knowledge: $LINK_NAME -> $RELATIVE_PATH" >> ~/.aircana/hooks.log
|
104
|
-
((LINKED_KNOWLEDGE++))
|
105
|
-
fi
|
106
|
-
done
|
107
|
-
fi
|
108
|
-
done
|
109
|
-
|
110
|
-
# Report results
|
111
|
-
echo "$(date): Multi-root setup complete - linked $LINKED_AGENTS agents and $LINKED_KNOWLEDGE knowledge bases" >> ~/.aircana/hooks.log
|
112
|
-
|
113
|
-
# Return success with optional context
|
114
|
-
if [ $LINKED_AGENTS -gt 0 ] || [ $LINKED_KNOWLEDGE -gt 0 ]; then
|
115
|
-
CONTEXT="Multi-root: Linked $LINKED_AGENTS agents and $LINKED_KNOWLEDGE knowledge bases from configured folders."
|
116
|
-
ESCAPED_CONTEXT=$(echo -n "$CONTEXT" | sed 's/"/\\"/g')
|
117
|
-
cat << EOF
|
118
|
-
{
|
119
|
-
"hookSpecificOutput": {
|
120
|
-
"hookEventName": "SessionStart",
|
121
|
-
"additionalContext": "$ESCAPED_CONTEXT"
|
122
|
-
}
|
123
|
-
}
|
124
|
-
EOF
|
125
|
-
else
|
126
|
-
echo "{}"
|
127
|
-
fi
|
11
|
+
# Return success
|
12
|
+
echo "{}"
|
@@ -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