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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.claude-plugin/plugin.json +7 -0
  3. data/.rspec_status +184 -187
  4. data/.rubocop.yml +12 -0
  5. data/CHANGELOG.md +38 -0
  6. data/CLAUDE.md +51 -20
  7. data/README.md +132 -63
  8. data/agents/apply_feedback.md +92 -0
  9. data/agents/executor.md +85 -0
  10. data/agents/jira.md +46 -0
  11. data/agents/planner.md +64 -0
  12. data/agents/reviewer.md +95 -0
  13. data/agents/sub-agent-coordinator.md +91 -0
  14. data/agents/test-agent/manifest.json +15 -0
  15. data/commands/air-apply-feedback.md +15 -0
  16. data/commands/air-ask-expert.md +42 -0
  17. data/commands/air-execute.md +13 -0
  18. data/commands/air-plan.md +33 -0
  19. data/commands/air-record.md +17 -0
  20. data/commands/air-review.md +12 -0
  21. data/commands/sample-command.md +1 -0
  22. data/hooks/hooks.json +31 -0
  23. data/lib/aircana/cli/app.rb +27 -30
  24. data/lib/aircana/cli/commands/agents.rb +41 -9
  25. data/lib/aircana/cli/commands/doctor_checks.rb +2 -3
  26. data/lib/aircana/cli/commands/generate.rb +0 -11
  27. data/lib/aircana/cli/commands/hooks.rb +4 -4
  28. data/lib/aircana/cli/commands/init.rb +266 -0
  29. data/lib/aircana/cli/commands/plugin.rb +157 -0
  30. data/lib/aircana/cli/help_formatter.rb +2 -3
  31. data/lib/aircana/configuration.rb +29 -3
  32. data/lib/aircana/contexts/manifest.rb +1 -8
  33. data/lib/aircana/generators/agents_generator.rb +3 -2
  34. data/lib/aircana/hooks_manifest.rb +189 -0
  35. data/lib/aircana/plugin_manifest.rb +146 -0
  36. data/lib/aircana/system_checker.rb +0 -11
  37. data/lib/aircana/templates/agents/base_agent.erb +2 -2
  38. data/lib/aircana/templates/hooks/session_start.erb +3 -118
  39. data/lib/aircana/templates/hooks/user_prompt_submit.erb +0 -6
  40. data/lib/aircana/version.rb +1 -1
  41. data/spec_target_1760205040_181/agents/test-agent/manifest.json +15 -0
  42. data/spec_target_1760205220_486/agents/test-agent/manifest.json +15 -0
  43. data/spec_target_1760205379_250/agents/test-agent/manifest.json +15 -0
  44. data/spec_target_1760205601_652/agents/test-agent/manifest.json +15 -0
  45. data/spec_target_1760205608_135/agents/test-agent/manifest.json +15 -0
  46. data/spec_target_1760205654_952/agents/test-agent/manifest.json +15 -0
  47. metadata +29 -7
  48. data/lib/aircana/cli/commands/install.rb +0 -179
  49. data/lib/aircana/cli/commands/project.rb +0 -156
  50. data/lib/aircana/generators/project_config_generator.rb +0 -54
  51. 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 .aircana/agents/#{agent_name}/knowledge/*.md` to list files from your knowledge base.
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 current project directory. Use `ls .aircana/agents/<%= agent_name %>/knowledge/*.md` to list available files, then read them with the Read tool.
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
- # Multi-root project support hook for Aircana
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
- # Check if jq is available
16
- if ! command -v jq &> /dev/null; then
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aircana
4
- VERSION = "2.0.0"
4
+ VERSION = "3.0.0.rc2"
5
5
  end
@@ -0,0 +1,15 @@
1
+ {
2
+ "version": "1.0",
3
+ "agent": "test-agent",
4
+ "sources": [
5
+ {
6
+ "type": "confluence",
7
+ "label": "test-agent",
8
+ "pages": [
9
+ {
10
+ "id": "123"
11
+ }
12
+ ]
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "version": "1.0",
3
+ "agent": "test-agent",
4
+ "sources": [
5
+ {
6
+ "type": "confluence",
7
+ "label": "test-agent",
8
+ "pages": [
9
+ {
10
+ "id": "123"
11
+ }
12
+ ]
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "version": "1.0",
3
+ "agent": "test-agent",
4
+ "sources": [
5
+ {
6
+ "type": "confluence",
7
+ "label": "test-agent",
8
+ "pages": [
9
+ {
10
+ "id": "123"
11
+ }
12
+ ]
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "version": "1.0",
3
+ "agent": "test-agent",
4
+ "sources": [
5
+ {
6
+ "type": "confluence",
7
+ "label": "test-agent",
8
+ "pages": [
9
+ {
10
+ "id": "123"
11
+ }
12
+ ]
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "version": "1.0",
3
+ "agent": "test-agent",
4
+ "sources": [
5
+ {
6
+ "type": "confluence",
7
+ "label": "test-agent",
8
+ "pages": [
9
+ {
10
+ "id": "123"
11
+ }
12
+ ]
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "version": "1.0",
3
+ "agent": "test-agent",
4
+ "sources": [
5
+ {
6
+ "type": "confluence",
7
+ "label": "test-agent",
8
+ "pages": [
9
+ {
10
+ "id": "123"
11
+ }
12
+ ]
13
+ }
14
+ ]
15
+ }