aircana 0.1.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 (59) hide show
  1. checksums.yaml +7 -0
  2. data/.devcontainer/devcontainer.json +36 -0
  3. data/.dockerignore +14 -0
  4. data/.rspec_status +106 -0
  5. data/.rubocop.yml +33 -0
  6. data/CHANGELOG.md +19 -0
  7. data/CLAUDE.md +58 -0
  8. data/Dockerfile +17 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +251 -0
  11. data/Rakefile +12 -0
  12. data/SECURITY.md +15 -0
  13. data/compose.yml +13 -0
  14. data/exe/aircana +13 -0
  15. data/lib/aircana/cli/app.rb +93 -0
  16. data/lib/aircana/cli/commands/add_directory.rb +148 -0
  17. data/lib/aircana/cli/commands/add_files.rb +26 -0
  18. data/lib/aircana/cli/commands/agents.rb +152 -0
  19. data/lib/aircana/cli/commands/clear_files.rb +16 -0
  20. data/lib/aircana/cli/commands/doctor.rb +85 -0
  21. data/lib/aircana/cli/commands/doctor_checks.rb +131 -0
  22. data/lib/aircana/cli/commands/doctor_helpers.rb +119 -0
  23. data/lib/aircana/cli/commands/dump_context.rb +23 -0
  24. data/lib/aircana/cli/commands/generate.rb +34 -0
  25. data/lib/aircana/cli/commands/install.rb +67 -0
  26. data/lib/aircana/cli/commands/plan.rb +69 -0
  27. data/lib/aircana/cli/commands/work.rb +69 -0
  28. data/lib/aircana/cli/shell_command.rb +13 -0
  29. data/lib/aircana/cli/subcommand.rb +19 -0
  30. data/lib/aircana/cli.rb +8 -0
  31. data/lib/aircana/configuration.rb +41 -0
  32. data/lib/aircana/contexts/confluence.rb +141 -0
  33. data/lib/aircana/contexts/confluence_content.rb +36 -0
  34. data/lib/aircana/contexts/confluence_http.rb +41 -0
  35. data/lib/aircana/contexts/confluence_logging.rb +71 -0
  36. data/lib/aircana/contexts/confluence_setup.rb +15 -0
  37. data/lib/aircana/contexts/local.rb +47 -0
  38. data/lib/aircana/contexts/relevant_files.rb +78 -0
  39. data/lib/aircana/fzf_helper.rb +117 -0
  40. data/lib/aircana/generators/agents_generator.rb +75 -0
  41. data/lib/aircana/generators/base_generator.rb +61 -0
  42. data/lib/aircana/generators/helpers.rb +16 -0
  43. data/lib/aircana/generators/relevant_files_command_generator.rb +36 -0
  44. data/lib/aircana/generators/relevant_files_verbose_results_generator.rb +34 -0
  45. data/lib/aircana/generators.rb +10 -0
  46. data/lib/aircana/human_logger.rb +143 -0
  47. data/lib/aircana/initializers.rb +8 -0
  48. data/lib/aircana/llm/claude_client.rb +86 -0
  49. data/lib/aircana/progress_tracker.rb +55 -0
  50. data/lib/aircana/system_checker.rb +177 -0
  51. data/lib/aircana/templates/agents/base_agent.erb +30 -0
  52. data/lib/aircana/templates/agents/defaults/planner.erb +126 -0
  53. data/lib/aircana/templates/agents/defaults/worker.erb +185 -0
  54. data/lib/aircana/templates/commands/add_relevant_files.erb +3 -0
  55. data/lib/aircana/templates/relevant_files_verbose_results.erb +18 -0
  56. data/lib/aircana/version.rb +5 -0
  57. data/lib/aircana.rb +53 -0
  58. data/sig/aircana.rbs +4 -0
  59. metadata +189 -0
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aircana
4
+ class HumanLogger
5
+ # Emoji mappings for different message types
6
+ EMOJIS = {
7
+ # Core status indicators
8
+ success: "✅",
9
+ error: "❌",
10
+ warning: "⚠️",
11
+ info: "ℹ️",
12
+
13
+ # Context-specific emojis
14
+ file: "📁",
15
+ files: "📁",
16
+ agent: "🤖",
17
+ network: "🌐",
18
+ page: "📄",
19
+ pages: "📄",
20
+ search: "🔍",
21
+ generation: "⚙️",
22
+
23
+ # Action emojis
24
+ created: "📝",
25
+ stored: "💾",
26
+ refresh: "🔄",
27
+ install: "📦",
28
+ found: "🔍",
29
+ added: "➕",
30
+ removed: "➖"
31
+ }.freeze
32
+
33
+ # Color codes for terminal output
34
+ COLORS = {
35
+ success: "\e[32m", # Green
36
+ error: "\e[31m", # Red
37
+ warning: "\e[33m", # Yellow
38
+ info: "\e[36m", # Cyan
39
+ reset: "\e[0m" # Reset
40
+ }.freeze
41
+
42
+ def initialize(output = $stdout)
43
+ @output = output
44
+ end
45
+
46
+ def success(message)
47
+ log_with_emoji_and_color(:success, message)
48
+ end
49
+
50
+ def error(message)
51
+ log_with_emoji_and_color(:error, message)
52
+ end
53
+
54
+ def warn(message)
55
+ log_with_emoji_and_color(:warning, message)
56
+ end
57
+
58
+ def info(message)
59
+ log_with_emoji_and_color(:info, message)
60
+ end
61
+
62
+ private
63
+
64
+ def log_with_emoji_and_color(level, message)
65
+ emoji = select_emoji(level, message)
66
+ color = COLORS[level]
67
+ reset = COLORS[:reset]
68
+
69
+ @output.puts "#{color}#{emoji} #{message}#{reset}"
70
+ end
71
+
72
+ def select_emoji(level, message)
73
+ # First check for context-specific emojis based on message content
74
+ context_emoji = detect_context_emoji(message)
75
+ return context_emoji if context_emoji
76
+
77
+ # Fall back to level-based emoji
78
+ EMOJIS[level] || EMOJIS[:info]
79
+ end
80
+
81
+ def detect_context_emoji(message)
82
+ message_lower = message.downcase
83
+ detect_context_based_emoji(message_lower) || detect_action_based_emoji(message_lower)
84
+ end
85
+
86
+ def detect_context_based_emoji(message_lower)
87
+ detect_content_emoji(message_lower) || detect_network_emoji(message_lower)
88
+ end
89
+
90
+ def detect_content_emoji(message_lower)
91
+ return EMOJIS[:agent] if message_lower.include?("agent")
92
+ return EMOJIS[:pages] if message_lower.match?(/\d+\s+pages?/)
93
+ return EMOJIS[:page] if message_lower.include?("page")
94
+ return EMOJIS[:files] if files_pattern_match?(message_lower)
95
+ return EMOJIS[:file] if single_file_pattern_match?(message_lower)
96
+
97
+ nil
98
+ end
99
+
100
+ def files_pattern_match?(message_lower)
101
+ message_lower.match?(/\d+\s+files?/) || message_lower.include?("directory")
102
+ end
103
+
104
+ def single_file_pattern_match?(message_lower)
105
+ message_lower.include?("file") && !message_lower.match?(/\d+\s+files?/)
106
+ end
107
+
108
+ def detect_network_emoji(message_lower)
109
+ if message_lower.include?("http") || message_lower.include?("network") || message_lower.include?("api")
110
+ return EMOJIS[:network]
111
+ end
112
+
113
+ nil
114
+ end
115
+
116
+ def detect_action_based_emoji(message_lower)
117
+ return EMOJIS[:created] if creation_keywords?(message_lower)
118
+ return EMOJIS[:stored] if storage_keywords?(message_lower)
119
+ return EMOJIS[:refresh] if refresh_keywords?(message_lower)
120
+ return EMOJIS[:install] if message_lower.include?("install")
121
+ return EMOJIS[:added] if success_keywords?(message_lower)
122
+ return EMOJIS[:found] if message_lower.include?("found")
123
+
124
+ nil
125
+ end
126
+
127
+ def creation_keywords?(message_lower)
128
+ message_lower.include?("created") || message_lower.include?("generating") || message_lower.include?("generated")
129
+ end
130
+
131
+ def storage_keywords?(message_lower)
132
+ message_lower.include?("stored") || message_lower.include?("saving")
133
+ end
134
+
135
+ def refresh_keywords?(message_lower)
136
+ message_lower.include?("refresh") || message_lower.include?("sync")
137
+ end
138
+
139
+ def success_keywords?(message_lower)
140
+ message_lower.include?("added") || message_lower.include?("successfully")
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "initializers/project_configuration"
4
+
5
+ module Aircana
6
+ module Initializers
7
+ end
8
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "English"
4
+ require "tty-spinner"
5
+
6
+ module Aircana
7
+ module LLM
8
+ class ClaudeClient
9
+ def initialize
10
+ @spinner = nil
11
+ end
12
+
13
+ def prompt(text)
14
+ start_spinner("Generating response with Claude...")
15
+
16
+ begin
17
+ result = execute_claude_command(text)
18
+ success_spinner("Generated response with Claude")
19
+ result.strip
20
+ rescue StandardError => e
21
+ error_spinner("Failed to generate response: #{e.message}")
22
+ raise Error, "Claude request failed: #{e.message}"
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def start_spinner(message)
29
+ @spinner = TTY::Spinner.new("[:spinner] #{message}", format: :dots)
30
+ @spinner.auto_spin
31
+ end
32
+
33
+ def success_spinner(message)
34
+ return unless @spinner
35
+
36
+ @spinner.stop("✓")
37
+ puts message
38
+ end
39
+
40
+ def error_spinner(message)
41
+ return unless @spinner
42
+
43
+ @spinner.stop("✗")
44
+ puts message
45
+ end
46
+
47
+ def execute_claude_command(text)
48
+ command = build_claude_command(text)
49
+ execute_system_command(command)
50
+ end
51
+
52
+ def execute_system_command(command)
53
+ result = `#{command}`
54
+
55
+ unless $CHILD_STATUS.success?
56
+ raise StandardError,
57
+ "Claude command failed with exit code #{$CHILD_STATUS.exitstatus}"
58
+ end
59
+
60
+ result
61
+ end
62
+
63
+ def build_claude_command(text)
64
+ escaped_text = text.gsub("'", "'\"'\"'")
65
+ claude_path = find_claude_path
66
+ "#{claude_path} -p '#{escaped_text}'"
67
+ end
68
+
69
+ def find_claude_path
70
+ # Try common locations for Claude Code binary
71
+ possible_paths = [
72
+ File.expand_path("~/.claude/local/claude"),
73
+ `which claude`.strip,
74
+ "/usr/local/bin/claude"
75
+ ]
76
+
77
+ possible_paths.each do |path|
78
+ return path if !path.empty? && File.executable?(path)
79
+ end
80
+
81
+ # Fallback to just 'claude' and hope it's in PATH
82
+ "claude"
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-spinner"
4
+ require "tty-progressbar"
5
+
6
+ module Aircana
7
+ class ProgressTracker
8
+ def self.with_spinner(message, success_message: nil, item: nil)
9
+ spinner = TTY::Spinner.new("[:spinner] #{message}", format: :dots, clear: true)
10
+ spinner.auto_spin
11
+
12
+ begin
13
+ result = yield(item)
14
+ spinner.success("✅ #{success_message || message}")
15
+ result
16
+ rescue StandardError => e
17
+ spinner.error("❌ #{message} failed")
18
+ raise e
19
+ end
20
+ end
21
+
22
+ def self.with_progress_bar(total, message, &)
23
+ return with_spinner(message, &) if total <= 1
24
+
25
+ create_and_use_progress_bar(total, message, &)
26
+ end
27
+
28
+ def self.create_and_use_progress_bar(total, message)
29
+ bar = TTY::ProgressBar.new(
30
+ "#{message} [:bar] :current/:total (:percent) :elapsed",
31
+ total: total,
32
+ bar_format: :box
33
+ )
34
+ yield(bar)
35
+ bar
36
+ end
37
+
38
+ def self.with_batch_progress(items, message, &)
39
+ total = items.size
40
+ return with_spinner("#{message} (#{total} item)", item: items.first, &) if total <= 1
41
+
42
+ process_batch_with_progress(items, total, message, &)
43
+ end
44
+
45
+ def self.process_batch_with_progress(items, total, message)
46
+ with_progress_bar(total, message) do |bar|
47
+ items.each_with_index do |item, index|
48
+ result = yield(item, index)
49
+ bar.advance(1)
50
+ result
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "English"
4
+ module Aircana
5
+ class SystemChecker
6
+ REQUIRED_COMMANDS = {
7
+ "fzf" => {
8
+ purpose: "interactive file selection",
9
+ install: {
10
+ "macOS" => "brew install fzf",
11
+ "Ubuntu/Debian" => "apt install fzf",
12
+ "Fedora/CentOS" => "dnf install fzf",
13
+ "Arch" => "pacman -S fzf",
14
+ "Other" => "https://github.com/junegunn/fzf#installation"
15
+ }
16
+ },
17
+ "git" => {
18
+ purpose: "version control operations",
19
+ install: {
20
+ "macOS" => "brew install git",
21
+ "Ubuntu/Debian" => "apt install git",
22
+ "Fedora/CentOS" => "dnf install git",
23
+ "Arch" => "pacman -S git",
24
+ "Other" => "https://git-scm.com/downloads"
25
+ }
26
+ }
27
+ }.freeze
28
+
29
+ OPTIONAL_COMMANDS = {
30
+ "bat" => {
31
+ purpose: "enhanced file previews",
32
+ fallback: "head/cat for basic previews",
33
+ install: {
34
+ "macOS" => "brew install bat",
35
+ "Ubuntu/Debian" => "apt install bat",
36
+ "Fedora/CentOS" => "dnf install bat",
37
+ "Arch" => "pacman -S bat",
38
+ "Other" => "https://github.com/sharkdp/bat#installation"
39
+ }
40
+ },
41
+ "fd" => {
42
+ purpose: "fast file searching",
43
+ fallback: "find command for basic file listing",
44
+ install: {
45
+ "macOS" => "brew install fd",
46
+ "Ubuntu/Debian" => "apt install fd-find",
47
+ "Fedora/CentOS" => "dnf install fd-find",
48
+ "Arch" => "pacman -S fd",
49
+ "Other" => "https://github.com/sharkdp/fd#installation"
50
+ }
51
+ }
52
+ }.freeze
53
+
54
+ class << self
55
+ def check_dependencies?(show_optional: false)
56
+ Aircana.human_logger.info "Checking system dependencies..."
57
+
58
+ missing_required = check_required_commands
59
+ missing_optional = check_optional_commands if show_optional
60
+
61
+ if missing_required.empty? && (missing_optional.nil? || missing_optional.empty?)
62
+ Aircana.human_logger.success "All dependencies satisfied!"
63
+ return true
64
+ end
65
+
66
+ show_installation_help(missing_required, missing_optional)
67
+ missing_required.empty? # Return true if no required dependencies missing
68
+ end
69
+
70
+ def command_available?(command)
71
+ system("which #{command}", out: File::NULL, err: File::NULL)
72
+ end
73
+
74
+ def claude_installed?
75
+ command_available?("claude")
76
+ end
77
+
78
+ def mcp_jira_installed?
79
+ return false unless claude_installed?
80
+
81
+ result = `claude mcp get jira 2>&1`
82
+ $CHILD_STATUS.success? && !result.include?("not found") && !result.include?("error")
83
+ rescue StandardError
84
+ false
85
+ end
86
+
87
+ def check_configuration_directories
88
+ {
89
+ global: File.expand_path("~/.aircana"),
90
+ project: File.join(Dir.pwd, ".aircana"),
91
+ claude_global: File.expand_path("~/.claude"),
92
+ claude_project: File.join(Dir.pwd, ".claude")
93
+ }.transform_values { |path| Dir.exist?(path) }
94
+ end
95
+
96
+ def detect_os
97
+ return "macOS" if macos?
98
+ return detect_linux_distribution if linux?
99
+
100
+ "Other"
101
+ end
102
+
103
+ def macos?
104
+ RbConfig::CONFIG["host_os"].match?(/darwin/)
105
+ end
106
+
107
+ def linux?
108
+ RbConfig::CONFIG["host_os"].match?(/linux/)
109
+ end
110
+
111
+ def detect_linux_distribution
112
+ return "Ubuntu/Debian" if File.exist?("/etc/debian_version")
113
+ return "Fedora/CentOS" if fedora_or_centos?
114
+ return "Arch" if File.exist?("/etc/arch-release")
115
+
116
+ "Other"
117
+ end
118
+
119
+ def fedora_or_centos?
120
+ File.exist?("/etc/fedora-release") || File.exist?("/etc/centos-release")
121
+ end
122
+
123
+ private
124
+
125
+ def check_required_commands
126
+ missing = []
127
+
128
+ REQUIRED_COMMANDS.each do |command, info|
129
+ unless command_available?(command)
130
+ Aircana.human_logger.error "Missing required dependency: #{command} (#{info[:purpose]})"
131
+ missing << command
132
+ end
133
+ end
134
+
135
+ missing
136
+ end
137
+
138
+ def check_optional_commands
139
+ missing = []
140
+
141
+ OPTIONAL_COMMANDS.each do |command, info|
142
+ next if command_available?(command)
143
+
144
+ Aircana.human_logger.warn "Optional dependency missing: #{command} (#{info[:purpose]})"
145
+ Aircana.human_logger.info " Fallback: #{info[:fallback]}"
146
+ missing << command
147
+ end
148
+
149
+ missing
150
+ end
151
+
152
+ def show_installation_help(missing_required, missing_optional)
153
+ return if no_missing_dependencies?(missing_required, missing_optional)
154
+
155
+ display_installation_instructions(missing_required, missing_optional)
156
+ end
157
+
158
+ def no_missing_dependencies?(missing_required, missing_optional)
159
+ missing_required.empty? && (missing_optional.nil? || missing_optional.empty?)
160
+ end
161
+
162
+ def display_installation_instructions(missing_required, missing_optional)
163
+ os = detect_os
164
+ Aircana.human_logger.info "Installation instructions for #{os}:"
165
+ show_commands_installation(missing_required, missing_optional, os)
166
+ end
167
+
168
+ def show_commands_installation(missing_required, missing_optional, os)
169
+ [missing_required, missing_optional].compact.flatten.each do |command|
170
+ command_info = REQUIRED_COMMANDS[command] || OPTIONAL_COMMANDS[command]
171
+ install_cmd = command_info[:install][os] || command_info[:install]["Other"]
172
+ Aircana.human_logger.info " #{command}: #{install_cmd}"
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: <%= agent_name %>
3
+ description: <%= short_description %>
4
+ model: <%= model %>
5
+ color: <%= color %>
6
+ ---
7
+ <%= description %>
8
+
9
+ <%= helpers.model_instructions("ALWAYS check your knowledge base FIRST for every query, task, or question you receive. Your knowledge is stored in: @#{knowledge_path}
10
+
11
+ MANDATORY WORKFLOW:
12
+ 1. BEFORE responding to ANY query - search and read relevant files in your knowledge base
13
+ 2. Base your responses primarily on information found in your knowledge base
14
+ 3. If knowledge base contains relevant information, cite specific files/sections
15
+ 4. Only use general knowledge when your knowledge base lacks specific information
16
+ 5. When combining knowledge base info with general knowledge, clearly distinguish between sources
17
+
18
+ Your knowledge base contains domain-specific information that takes priority over general AI knowledge.", important: true) %>
19
+
20
+ ## Knowledge Base Integration
21
+
22
+ Your specialized knowledge is stored in: @<%= knowledge_path %>
23
+
24
+ This knowledge base contains:
25
+ - Domain-specific documentation from Confluence
26
+ - Project-specific processes and procedures
27
+ - Team standards and best practices
28
+ - Technical specifications and requirements
29
+
30
+ Always consult this knowledge base before making recommendations or providing guidance.
@@ -0,0 +1,126 @@
1
+ ---
2
+ name: planner
3
+ description: Specialized agent for creating implementation plans for Jira tickets
4
+ model: inherit
5
+ color: blue
6
+ ---
7
+ # Planner Agent
8
+
9
+ You are the Planner Agent, specialized in creating comprehensive implementation plans for Jira tickets through AI-powered collaboration with expert agents.
10
+
11
+ <%= helpers.model_instructions("ALWAYS check your knowledge base FIRST for every query, task, or question you receive. Your knowledge is stored in: @#{knowledge_path}
12
+
13
+ MANDATORY WORKFLOW FOR KNOWLEDGE:
14
+ 1. BEFORE starting any planning - search and read relevant files in your knowledge base
15
+ 2. Look for existing planning templates, processes, and methodologies
16
+ 3. Check for similar ticket plans or implementation patterns
17
+ 4. Review project-specific requirements and constraints
18
+ 5. Base your planning approach on knowledge base guidance when available
19
+ 6. Incorporate knowledge base standards into your execution plans
20
+
21
+ Your knowledge base contains domain-specific information that takes priority over general planning knowledge.", important: true) %>
22
+
23
+ ## Core Responsibilities
24
+
25
+ You create detailed implementation plans by:
26
+ 1. Gathering ticket requirements from Jira
27
+ 2. Collecting relevant project files for context
28
+ 3. Coordinating with expert agents for specialized analysis
29
+ 4. Generating comprehensive execution plans
30
+ 5. Creating actionable todo lists
31
+ 6. Storing plans in structured format
32
+
33
+ ## Planning Workflow
34
+
35
+ Follow this exact sequence for every planning session:
36
+
37
+ ### Step A: Ticket Identification
38
+ - Prompt user for Jira ticket ID to plan for
39
+ - If no ticket provided, ask if they want to create a new one.
40
+ - Use MCP Jira tools to fetch ticket details
41
+
42
+ **IMMEDIATELY: Check Knowledge Base**
43
+ - Search your knowledge base (@<%= knowledge_path %>) for:
44
+ - Similar tickets or implementations
45
+ - Relevant planning methodologies
46
+ - Project-specific planning templates
47
+ - Domain-specific requirements and constraints
48
+
49
+ ### Step B: Check Existing Plan
50
+ - Examine ticket description for "# AGENT PLAN #" section
51
+ - If found, present existing plan to user
52
+ - Ask if adjustments are needed to current plan
53
+
54
+ ### Step C: Gather Context (if no plan exists)
55
+ - **FIRST: Ask user for relevant files**
56
+ - Prompt: "What files are most relevant to this ticket? Please list file paths."
57
+ - These files provide crucial context before expert consultation
58
+ - Read and analyze provided relevant files
59
+ - Extract key patterns, frameworks, and architectural decisions
60
+
61
+ ### Step D: Expert Agent Coordination
62
+ - Based on ticket requirements, relevant files, and knowledge base findings, identify expert agents
63
+ - **BEFORE consulting experts**: Review knowledge base for existing expert guidance on similar topics
64
+ - Use Claude Code's Task tool to consult expert agents in parallel
65
+ - Ask each expert to provide key details for the plan in their domain of expertise
66
+ - **Combine knowledge base findings with expert recommendations** for comprehensive planning
67
+
68
+ ### Step E: Plan Generation
69
+ - Consolidate expert recommendations into cohesive execution plan
70
+ - Create detailed todo list including:
71
+ - Implementation steps
72
+ - Unit test writing requirements
73
+ - Test execution and iteration cycles
74
+ - Present plan to user for approval and adjustments
75
+
76
+ ### Step F: Plan Storage
77
+ - Format plan with required sections (see below)
78
+ - If new ticket needed, ask for Jira project and create ticket
79
+ - Update/create Jira ticket with complete plan in description. Do not search yourself
80
+ for an appropriate Jira project. Always ask the user what Jira project the ticket
81
+ should be created in.
82
+
83
+ ## Plan Format
84
+
85
+ Store plans in Jira ticket descriptions using this exact structure:
86
+
87
+ ```markdown
88
+ # AGENT PLAN #
89
+
90
+ ## Relevant Files
91
+ - path/to/file1.rb
92
+ - path/to/file2.rb
93
+ - directory/file3.rb
94
+
95
+ ## Expert Agents
96
+ - agent-name-1
97
+ - agent-name-2
98
+
99
+ ## Execution Plan
100
+ [Detailed implementation steps from expert analysis]
101
+
102
+ ## Todo List
103
+ - [ ] Task 1
104
+ - [ ] Task 2
105
+ - [ ] Write unit tests for [specific functionality]
106
+ - [ ] Run tests and iterate until all pass
107
+ - [ ] Final integration testing
108
+ ```
109
+
110
+ ## Key Instructions
111
+
112
+ 1. **Always ask for relevant files BEFORE consulting expert agents**
113
+ 2. **Use the exact "# AGENT PLAN #" header for plan sections**
114
+ 3. **Include test writing and execution in every todo list**
115
+ 4. **Present plans to users for approval before saving**
116
+ 5. **Use MCP Jira tools for all ticket operations**
117
+ 6. **Coordinate expert agents using Claude Code's Task tool**
118
+
119
+ ## Error Handling
120
+
121
+ - If Jira access fails, guide user to check MCP Jira configuration
122
+ - If no expert agents found, proceed with general analysis
123
+ - If file access fails, note in plan and continue
124
+ - Always provide fallback options when tools fail
125
+
126
+ Start every planning session by confirming: "I'm ready to help you create an implementation plan. Do you have a Jira ticket ID to plan for, or would you like to create a new ticket?"