code_healer 0.1.19 → 0.1.20
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/CHANGELOG.md +17 -1
- data/bin/start_claude_session +16 -0
- data/code_healer.gemspec +2 -0
- data/config/code_healer.yml.example +33 -1
- data/lib/code_healer/business_context_manager.rb +57 -3
- data/lib/code_healer/claude_code_evolution_handler.rb +13 -12
- data/lib/code_healer/config_manager.rb +51 -0
- data/lib/code_healer/healing_job.rb +44 -0
- data/lib/code_healer/mcp_server.rb +222 -2
- data/lib/code_healer/mcp_tools.rb +390 -5
- data/lib/code_healer/setup.rb +141 -2
- data/lib/code_healer/version.rb +1 -1
- data/lib/code_healer.rb +4 -1
- metadata +23 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c54944cc1cad8a6a188bbf5b5a50a216b0719046339b59eccc12447aff916582
|
4
|
+
data.tar.gz: f190123758f3d4b73a7c7f53ccf8b275347819e5a7b60008085a62a79c239751
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 050def7293a77fb6204fb51f9fc5640caf036903b765c34be1b9c9163300e1034ae880ad3855d92480df40fe813c4590df6c1a86b812d80c9e541b8d7df8d9ef
|
7
|
+
data.tar.gz: 1d2874cf1817fdf4825d189f666c5fd9e9a300966892118dd79e254a4b528251d8cb85e587525735a3749f07ad75711e1ac1cdd444f30ba0c76852d4d4f62504
|
data/CHANGELOG.md
CHANGED
@@ -5,7 +5,23 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
-
## [
|
8
|
+
## [0.1.20] - 2025-08-27
|
9
|
+
|
10
|
+
### Added
|
11
|
+
- **Confluence MCP Integration**: Added direct Confluence MCP tools for business context fetching (optional usage)
|
12
|
+
- **Flexible MCP Usage**: Enhanced prompts to optionally use Confluence MCP tools when available
|
13
|
+
- **Non-interactive MCP**: Fixed Claude Terminal flags to avoid manual approval prompts
|
14
|
+
- **Business Context Strategy**: Added `confluence_only` strategy for focused Confluence documentation usage
|
15
|
+
|
16
|
+
### Fixed
|
17
|
+
- **MCP Tool Access**: Removed restrictive tool flags that blocked MCP tool usage
|
18
|
+
- **Debug Logging**: Cleaned up unnecessary MCP debugging logs from initialization and job startup
|
19
|
+
- **Command Optimization**: Fixed Claude Terminal command flags for proper MCP integration
|
20
|
+
|
21
|
+
### Changed
|
22
|
+
- **Prompt Strategy**: Updated business context prompts to optionally use MCP tools when available
|
23
|
+
- **Initialization**: Streamlined gem startup without MCP availability checks
|
24
|
+
- **Dependencies**: Maintained `httparty` for MCP API integration while removing debug overhead
|
9
25
|
|
10
26
|
## [0.1.19] - 2025-08-21
|
11
27
|
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Start Claude Terminal with code_healer_session for MCP tools access
|
4
|
+
# This session will be used by CodeHealer for healing jobs
|
5
|
+
|
6
|
+
puts "🚀 Starting Claude Terminal with code_healer_session for MCP tools..."
|
7
|
+
puts "This session will be used by CodeHealer for healing jobs with Jira/Confluence access"
|
8
|
+
puts ""
|
9
|
+
puts "Session name: code_healer_session"
|
10
|
+
puts "MCP tools: Jira, Confluence, and other Atlassian integrations"
|
11
|
+
puts ""
|
12
|
+
puts "Keep this terminal open while running healing jobs!"
|
13
|
+
puts "=" * 60
|
14
|
+
|
15
|
+
# Start Claude Terminal with the session
|
16
|
+
exec "claude --session code_healer_session"
|
data/code_healer.gemspec
CHANGED
@@ -60,6 +60,8 @@ Gem::Specification.new do |spec|
|
|
60
60
|
spec.add_runtime_dependency 'activesupport', '>= 6.0.0'
|
61
61
|
spec.add_runtime_dependency 'actionpack', '>= 6.0.0'
|
62
62
|
spec.add_runtime_dependency 'activemodel', '>= 6.0.0'
|
63
|
+
spec.add_runtime_dependency 'httparty', '~> 0.21.0', '>= 0.21.0'
|
64
|
+
|
63
65
|
|
64
66
|
# Development dependencies
|
65
67
|
spec.add_development_dependency "bundler", ">= 2.0.0"
|
@@ -49,7 +49,39 @@ claude_code:
|
|
49
49
|
business_context:
|
50
50
|
enabled: true
|
51
51
|
|
52
|
-
#
|
52
|
+
# Business Context Strategy
|
53
|
+
strategy: "jira_mcp" # Options: "jira_mcp", "markdown", "hybrid"
|
54
|
+
|
55
|
+
# Jira MCP Configuration (when strategy is "jira_mcp" or "hybrid")
|
56
|
+
jira_mcp:
|
57
|
+
enabled: true
|
58
|
+
project_key: "DGTL" # Your Jira project key
|
59
|
+
search_tickets_on_error: true
|
60
|
+
include_business_rules: true
|
61
|
+
system_prompt: |
|
62
|
+
When fixing code, ALWAYS check Jira MCP for business context:
|
63
|
+
1. Search for tickets about the class/method you're fixing
|
64
|
+
2. Use Jira requirements to ensure your fix follows business rules
|
65
|
+
3. Reference specific Jira tickets in your explanation
|
66
|
+
4. Make sure fixes align with business requirements
|
67
|
+
|
68
|
+
# Markdown Configuration (when strategy is "markdown" or "hybrid")
|
69
|
+
markdown:
|
70
|
+
enabled: true
|
71
|
+
search_paths:
|
72
|
+
- "docs/business_rules.md"
|
73
|
+
- "docs/requirements.md"
|
74
|
+
- "business_requirements/"
|
75
|
+
include_patterns:
|
76
|
+
- "*.md"
|
77
|
+
- "*.txt"
|
78
|
+
|
79
|
+
# Hybrid Configuration (when strategy is "hybrid")
|
80
|
+
hybrid:
|
81
|
+
priority: ["jira_mcp", "markdown"] # Which source to check first
|
82
|
+
combine_results: true # Merge results from both sources
|
83
|
+
|
84
|
+
# Define business rules for specific classes (fallback when MCP unavailable)
|
53
85
|
User:
|
54
86
|
domain: "User Management"
|
55
87
|
key_rules:
|
@@ -177,8 +177,44 @@ module CodeHealer
|
|
177
177
|
end
|
178
178
|
|
179
179
|
def build_claude_code_prompt(error, class_name, method_name, file_path)
|
180
|
-
#
|
181
|
-
business_context =
|
180
|
+
# Get business context based on configured strategy
|
181
|
+
business_context = case CodeHealer::ConfigManager.business_context_strategy
|
182
|
+
when 'confluence_only'
|
183
|
+
"## Business Context Instructions:\n" \
|
184
|
+
"OPTIONAL: You can use Confluence MCP tools to enhance business context if available.\n\n" \
|
185
|
+
"1. If Confluence MCP is available:\n" \
|
186
|
+
" - Search for PRDs/specs related to '#{class_name}' / '#{method_name}'.\n" \
|
187
|
+
" - Query terms: '#{class_name}', '#{method_name}', 'business rules', 'validation patterns'.\n" \
|
188
|
+
" - Apply any found documentation to your fix.\n\n" \
|
189
|
+
"2. If Confluence MCP is not available:\n" \
|
190
|
+
" - Proceed with standard business logic and error handling practices.\n" \
|
191
|
+
" - Use common validation patterns and best practices.\n\n" \
|
192
|
+
"Note: MCP usage is optional - proceed with or without it."
|
193
|
+
when 'claude_atlassian_mcp'
|
194
|
+
"## Business Context Instructions:\n" \
|
195
|
+
"OPTIONAL: You can use Atlassian MCP tools to enhance business context if available.\n\n" \
|
196
|
+
"1. **If Jira MCP is available:**\n" \
|
197
|
+
" - Search for tickets related to '#{class_name}' or '#{method_name}'\n" \
|
198
|
+
" - Look for bug reports, requirements, or business rules\n" \
|
199
|
+
" - Apply any found ticket context to your fix\n\n" \
|
200
|
+
"2. **If Confluence MCP is available:**\n" \
|
201
|
+
" - Search for PRDs, technical specs, or business process docs\n" \
|
202
|
+
" - Look for domain-specific business rules related to '#{class_name}'\n" \
|
203
|
+
" - Apply any found documentation to your fix\n\n" \
|
204
|
+
"3. **If MCP tools are not available:**\n" \
|
205
|
+
" - Proceed with standard business logic and error handling practices\n" \
|
206
|
+
" - Use common validation patterns and best practices\n\n" \
|
207
|
+
"Note: MCP usage is optional - proceed with or without it."
|
208
|
+
when 'jira_mcp'
|
209
|
+
# Use Jira MCP context
|
210
|
+
get_jira_business_context(class_name)
|
211
|
+
when 'markdown'
|
212
|
+
# Use markdown files
|
213
|
+
load_business_context_from_markdown_simple
|
214
|
+
else
|
215
|
+
# Default fallback
|
216
|
+
load_business_context_from_markdown_simple
|
217
|
+
end
|
182
218
|
|
183
219
|
prompt = <<~PROMPT
|
184
220
|
I have a Ruby on Rails application with an error that needs fixing.
|
@@ -195,7 +231,7 @@ module CodeHealer
|
|
195
231
|
#{error.backtrace&.join("\n") || "No backtrace available"}
|
196
232
|
```
|
197
233
|
|
198
|
-
## Business Context
|
234
|
+
## Business Context:
|
199
235
|
#{business_context}
|
200
236
|
|
201
237
|
## Instructions:
|
@@ -262,6 +298,24 @@ module CodeHealer
|
|
262
298
|
prompt.strip
|
263
299
|
end
|
264
300
|
|
301
|
+
def get_jira_business_context(class_name)
|
302
|
+
# Get Jira business context using MCP tools
|
303
|
+
begin
|
304
|
+
# This would integrate with Jira MCP tools
|
305
|
+
# For now, return instructions to use Jira MCP
|
306
|
+
"## Jira Business Context Instructions:\n" \
|
307
|
+
"Use Jira MCP tools to fetch business context:\n" \
|
308
|
+
"1. Search for tickets related to '#{class_name}'\n" \
|
309
|
+
"2. Look for business requirements and rules\n" \
|
310
|
+
"3. Apply the context to your fix\n" \
|
311
|
+
"4. Reference specific Jira tickets in your explanation"
|
312
|
+
rescue => e
|
313
|
+
"## Jira Business Context:\n" \
|
314
|
+
"Unable to fetch Jira context: #{e.message}\n" \
|
315
|
+
"Please use Jira MCP tools manually to get business context."
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
265
319
|
private
|
266
320
|
|
267
321
|
def load_business_context_from_markdown_simple
|
@@ -110,31 +110,30 @@ module CodeHealer
|
|
110
110
|
def build_claude_command(prompt, config)
|
111
111
|
# Escape prompt for shell
|
112
112
|
escaped_prompt = prompt.gsub("'", "'\"'\"'")
|
113
|
-
|
114
|
-
# Build command template
|
113
|
+
|
114
|
+
# Build command template for MCP tools access
|
115
115
|
command_template = config['command_template'] || "claude --code '{prompt}'"
|
116
|
-
|
116
|
+
|
117
117
|
# Replace placeholder
|
118
118
|
command = command_template.gsub('{prompt}', escaped_prompt)
|
119
|
-
|
120
|
-
#
|
119
|
+
|
120
|
+
# [rest of your existing code remains the same]
|
121
121
|
if config['include_tests'] && !CodeHealer::ConfigManager.demo_mode?
|
122
122
|
command += " --append-system-prompt 'Include tests when fixing the code'"
|
123
123
|
else
|
124
124
|
command += " --append-system-prompt 'Do NOT create or modify tests'"
|
125
125
|
end
|
126
|
-
|
126
|
+
|
127
127
|
if config['max_file_changes']
|
128
128
|
command += " --append-system-prompt 'Limit changes to #{config['max_file_changes']} files maximum'"
|
129
129
|
end
|
130
|
-
|
131
|
-
# Add file editing permissions
|
130
|
+
|
132
131
|
command += " --permission-mode acceptEdits --allowedTools Edit"
|
133
|
-
|
134
|
-
# Add current directory access but advise not to scan everything
|
132
|
+
command += " --append-system-prompt 'Optionally use Confluence MCP tools if available to enhance business context, but proceed regardless.'"
|
135
133
|
command += " --add-dir . --append-system-prompt 'Do not scan the whole repo; open only files explicitly referenced.'"
|
136
|
-
|
137
|
-
command
|
134
|
+
|
135
|
+
# Return combined command
|
136
|
+
mcp_setup + command
|
138
137
|
end
|
139
138
|
|
140
139
|
def reload_modified_files
|
@@ -194,6 +193,8 @@ module CodeHealer
|
|
194
193
|
puts "📝 Evolution attempt logged to #{log_file}"
|
195
194
|
end
|
196
195
|
|
196
|
+
|
197
|
+
|
197
198
|
def trigger_git_operations(error, class_name, method_name, file_path)
|
198
199
|
puts "🚀 Triggering Git operations for Claude Code evolution..."
|
199
200
|
|
@@ -107,6 +107,57 @@ module CodeHealer
|
|
107
107
|
config['business_context'] || {}
|
108
108
|
end
|
109
109
|
|
110
|
+
# Business Context Strategy Configuration
|
111
|
+
def business_context_strategy
|
112
|
+
business_context_settings['strategy'] || 'markdown'
|
113
|
+
end
|
114
|
+
|
115
|
+
def use_jira_mcp?
|
116
|
+
business_context_strategy == 'jira_mcp' || business_context_strategy == 'hybrid'
|
117
|
+
end
|
118
|
+
|
119
|
+
def use_markdown_context?
|
120
|
+
business_context_strategy == 'markdown' || business_context_strategy == 'hybrid'
|
121
|
+
end
|
122
|
+
|
123
|
+
def use_hybrid_context?
|
124
|
+
business_context_strategy == 'hybrid'
|
125
|
+
end
|
126
|
+
|
127
|
+
def jira_mcp_settings
|
128
|
+
business_context_settings['jira_mcp'] || {}
|
129
|
+
end
|
130
|
+
|
131
|
+
def markdown_settings
|
132
|
+
business_context_settings['markdown'] || {}
|
133
|
+
end
|
134
|
+
|
135
|
+
def hybrid_settings
|
136
|
+
business_context_settings['hybrid'] || {}
|
137
|
+
end
|
138
|
+
|
139
|
+
def jira_mcp_enabled?
|
140
|
+
use_jira_mcp? && jira_mcp_settings['enabled'] != false
|
141
|
+
end
|
142
|
+
|
143
|
+
def markdown_enabled?
|
144
|
+
use_markdown_context? && markdown_settings['enabled'] != false
|
145
|
+
end
|
146
|
+
|
147
|
+
def jira_mcp_system_prompt
|
148
|
+
jira_mcp_settings['system_prompt'] || default_jira_mcp_prompt
|
149
|
+
end
|
150
|
+
|
151
|
+
def default_jira_mcp_prompt
|
152
|
+
<<~PROMPT
|
153
|
+
When fixing code, ALWAYS check Jira MCP for business context:
|
154
|
+
1. Search for tickets about the class/method you're fixing
|
155
|
+
2. Use Jira requirements to ensure your fix follows business rules
|
156
|
+
3. Reference specific Jira tickets in your explanation
|
157
|
+
4. Make sure fixes align with business requirements
|
158
|
+
PROMPT
|
159
|
+
end
|
160
|
+
|
110
161
|
# API Configuration
|
111
162
|
def api_settings
|
112
163
|
config['api'] || {}
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'sidekiq'
|
2
|
+
require 'open3'
|
2
3
|
|
3
4
|
module CodeHealer
|
4
5
|
class HealingJob
|
@@ -9,6 +10,8 @@ module CodeHealer
|
|
9
10
|
def perform(*args)
|
10
11
|
puts "🚀 [HEALING_JOB] Starting job with args: #{args.inspect}"
|
11
12
|
|
13
|
+
|
14
|
+
|
12
15
|
# Support both legacy and new invocation styles
|
13
16
|
error, class_name, method_name, evolution_method, backtrace = parse_args(args)
|
14
17
|
|
@@ -127,6 +130,47 @@ module CodeHealer
|
|
127
130
|
end
|
128
131
|
|
129
132
|
private
|
133
|
+
|
134
|
+
def log_mcp_tools_availability
|
135
|
+
puts "🔍 [HEALING_JOB] Checking MCP tools availability..."
|
136
|
+
|
137
|
+
begin
|
138
|
+
# Check if Claude has MCP tools available
|
139
|
+
mcp_check_command = "claude --print 'List all available MCP tools' --output-format text"
|
140
|
+
stdout, stderr, status = Open3.capture3(mcp_check_command)
|
141
|
+
|
142
|
+
if status.success?
|
143
|
+
puts "✅ [HEALING_JOB] Claude Terminal is available"
|
144
|
+
|
145
|
+
# Extract MCP tools from output
|
146
|
+
if stdout.include?("MCP tools available") || stdout.include?("mcp__")
|
147
|
+
puts "🔧 [HEALING_JOB] MCP tools detected in Claude Terminal"
|
148
|
+
|
149
|
+
# Log specific MCP tools if found
|
150
|
+
if stdout.include?("mcp__atlassian")
|
151
|
+
puts " - Atlassian MCP tools: Available"
|
152
|
+
puts " - Jira integration: Available"
|
153
|
+
puts " - Confluence integration: Available"
|
154
|
+
end
|
155
|
+
|
156
|
+
if stdout.include?("mcp__")
|
157
|
+
puts " - Other MCP tools: Available"
|
158
|
+
end
|
159
|
+
else
|
160
|
+
puts "⚠️ [HEALING_JOB] No MCP tools detected in Claude Terminal"
|
161
|
+
puts "💡 Make sure Claude Terminal has MCP tools configured"
|
162
|
+
end
|
163
|
+
else
|
164
|
+
puts "❌ [HEALING_JOB] Claude Terminal is not available"
|
165
|
+
puts "💡 Make sure Claude Terminal is installed and accessible"
|
166
|
+
end
|
167
|
+
rescue => e
|
168
|
+
puts "⚠️ [HEALING_JOB] Could not check MCP tools: #{e.message}"
|
169
|
+
puts "💡 Make sure Claude Terminal is properly installed"
|
170
|
+
end
|
171
|
+
|
172
|
+
puts "🔍 [HEALING_JOB] MCP tools check complete"
|
173
|
+
end
|
130
174
|
|
131
175
|
def parse_args(args)
|
132
176
|
# Formats supported:
|
@@ -18,7 +18,9 @@ module CodeHealer
|
|
18
18
|
tools: [
|
19
19
|
ErrorAnalysisTool,
|
20
20
|
CodeFixTool,
|
21
|
-
ContextAnalysisTool
|
21
|
+
ContextAnalysisTool,
|
22
|
+
JIRAIntegrationTool, # Add Jira integration tool
|
23
|
+
ConfluenceIntegrationTool # Add Confluence integration tool
|
22
24
|
],
|
23
25
|
server_context: {
|
24
26
|
codebase_context: @codebase_context,
|
@@ -237,13 +239,231 @@ module CodeHealer
|
|
237
239
|
end
|
238
240
|
|
239
241
|
def get_business_context(class_name)
|
240
|
-
{
|
242
|
+
base_context = {
|
241
243
|
domain: determine_business_domain(class_name),
|
242
244
|
criticality: assess_business_criticality(class_name),
|
243
245
|
regulatory_requirements: identify_regulatory_requirements(class_name),
|
244
246
|
sla_requirements: get_sla_requirements(class_name),
|
245
247
|
user_impact: assess_user_impact(class_name)
|
246
248
|
}
|
249
|
+
|
250
|
+
# Get business context based on configured sources
|
251
|
+
business_context = get_configured_business_context(class_name)
|
252
|
+
base_context.merge!(business_context)
|
253
|
+
|
254
|
+
base_context
|
255
|
+
end
|
256
|
+
|
257
|
+
def get_configured_business_context(class_name)
|
258
|
+
context = {}
|
259
|
+
|
260
|
+
# Get business context based on configured strategy
|
261
|
+
case CodeHealer::ConfigManager.business_context_strategy
|
262
|
+
when 'jira_mcp'
|
263
|
+
if CodeHealer::ConfigManager.jira_mcp_enabled?
|
264
|
+
context[:strategy] = 'jira_mcp'
|
265
|
+
context[:instructions] = CodeHealer::ConfigManager.jira_mcp_system_prompt
|
266
|
+
context[:project_key] = CodeHealer::ConfigManager.jira_mcp_settings['project_key']
|
267
|
+
end
|
268
|
+
when 'markdown'
|
269
|
+
if CodeHealer::ConfigManager.markdown_enabled?
|
270
|
+
markdown_context = get_markdown_business_context(class_name)
|
271
|
+
context[:strategy] = 'markdown'
|
272
|
+
context[:markdown_context] = markdown_context
|
273
|
+
end
|
274
|
+
when 'hybrid'
|
275
|
+
# Combine both approaches
|
276
|
+
context[:strategy] = 'hybrid'
|
277
|
+
if CodeHealer::ConfigManager.jira_mcp_enabled?
|
278
|
+
context[:jira_mcp_instructions] = CodeHealer::ConfigManager.jira_mcp_system_prompt
|
279
|
+
context[:project_key] = CodeHealer::ConfigManager.jira_mcp_settings['project_key']
|
280
|
+
end
|
281
|
+
if CodeHealer::ConfigManager.markdown_enabled?
|
282
|
+
markdown_context = get_markdown_business_context(class_name)
|
283
|
+
context[:markdown_context] = markdown_context
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
context
|
288
|
+
end
|
289
|
+
|
290
|
+
def get_jira_business_context(class_name)
|
291
|
+
return {} unless defined?(JIRAIntegrationTool)
|
292
|
+
|
293
|
+
begin
|
294
|
+
# Use MCP tool to get Jira context
|
295
|
+
result = JIRAIntegrationTool.call(
|
296
|
+
action: "search_tickets",
|
297
|
+
search_query: "#{class_name} business rules requirements",
|
298
|
+
project_key: get_default_jira_project_key,
|
299
|
+
server_context: {}
|
300
|
+
)
|
301
|
+
|
302
|
+
if result && result.content.any?
|
303
|
+
data = JSON.parse(result.content.first[:text])
|
304
|
+
return {} if data['error']
|
305
|
+
|
306
|
+
# Extract business context from Jira tickets
|
307
|
+
{
|
308
|
+
related_tickets: data['tickets']&.first(3) || [],
|
309
|
+
business_rules: extract_business_rules_from_tickets(data['tickets']),
|
310
|
+
requirements: extract_requirements_from_tickets(data['tickets'])
|
311
|
+
}
|
312
|
+
end
|
313
|
+
rescue => e
|
314
|
+
puts "⚠️ Failed to get Jira business context: #{e.message}"
|
315
|
+
end
|
316
|
+
|
317
|
+
{}
|
318
|
+
end
|
319
|
+
|
320
|
+
def get_default_jira_project_key
|
321
|
+
# Extract from environment or use default
|
322
|
+
ENV['JIRA_PROJECT_KEY'] || 'DGTL'
|
323
|
+
end
|
324
|
+
|
325
|
+
def extract_business_rules_from_tickets(tickets)
|
326
|
+
return [] unless tickets
|
327
|
+
|
328
|
+
tickets.flat_map do |ticket|
|
329
|
+
# Extract business rules from ticket summary, description, labels
|
330
|
+
rules = []
|
331
|
+
rules << ticket['summary'] if ticket['summary']&.include?('rule')
|
332
|
+
rules << ticket['summary'] if ticket['summary']&.include?('policy')
|
333
|
+
rules << ticket['summary'] if ticket['summary']&.include?('requirement')
|
334
|
+
rules
|
335
|
+
end.compact.uniq
|
336
|
+
end
|
337
|
+
|
338
|
+
def extract_requirements_from_tickets(tickets)
|
339
|
+
return [] unless tickets
|
340
|
+
|
341
|
+
tickets.flat_map do |ticket|
|
342
|
+
# Extract business context from ticket content
|
343
|
+
requirements = []
|
344
|
+
requirements << ticket['summary'] if ticket['summary']&.include?('requirement')
|
345
|
+
requirements << ticket['summary'] if ticket['summary']&.include?('should')
|
346
|
+
requirements << ticket['summary'] if ticket['summary']&.include?('need')
|
347
|
+
requirements
|
348
|
+
end.compact.uniq
|
349
|
+
end
|
350
|
+
|
351
|
+
# Public method for Claude Terminal to access Confluence business context
|
352
|
+
def get_confluence_business_context(class_name)
|
353
|
+
return {} unless defined?(ConfluenceIntegrationTool)
|
354
|
+
|
355
|
+
begin
|
356
|
+
# Use MCP tool to get Confluence context
|
357
|
+
result = ConfluenceIntegrationTool.call(
|
358
|
+
action: "search_documents",
|
359
|
+
search_query: "#{class_name} PRD requirements business rules",
|
360
|
+
space_key: get_default_confluence_space_key,
|
361
|
+
server_context: {}
|
362
|
+
)
|
363
|
+
|
364
|
+
if result && result.content.any?
|
365
|
+
data = JSON.parse(result.content.first[:text])
|
366
|
+
return {} if data['error']
|
367
|
+
|
368
|
+
# Extract business context from Confluence documents
|
369
|
+
{
|
370
|
+
related_documents: data['documents']&.first(3) || [],
|
371
|
+
prd_content: extract_prd_content(data['documents']),
|
372
|
+
business_processes: extract_business_processes(data['documents'])
|
373
|
+
}
|
374
|
+
end
|
375
|
+
rescue => e
|
376
|
+
puts "⚠️ Failed to get Confluence business context: #{e.message}"
|
377
|
+
end
|
378
|
+
|
379
|
+
{}
|
380
|
+
end
|
381
|
+
|
382
|
+
# Public method for Claude Terminal to get Confluence space key
|
383
|
+
def get_default_confluence_space_key
|
384
|
+
# Extract from environment or use default
|
385
|
+
ENV['CONFLUENCE_SPACE_KEY'] || 'DGTL'
|
386
|
+
end
|
387
|
+
|
388
|
+
# Public method for Claude Terminal to extract PRD content
|
389
|
+
def extract_prd_content(documents)
|
390
|
+
return [] unless documents
|
391
|
+
|
392
|
+
documents.flat_map do |doc|
|
393
|
+
# Extract PRD content from document title, content, labels
|
394
|
+
content = []
|
395
|
+
content << doc['title'] if doc['title']&.include?('PRD')
|
396
|
+
content << doc['title'] if doc['title']&.include?('Product Requirements')
|
397
|
+
content << doc['title'] if doc['title']&.include?('Requirements')
|
398
|
+
content << doc['content']&.truncate(200) if doc['content']
|
399
|
+
content
|
400
|
+
end.compact.uniq
|
401
|
+
end
|
402
|
+
|
403
|
+
# Public method for Claude Terminal to extract business processes
|
404
|
+
def extract_business_processes(documents)
|
405
|
+
return [] unless documents
|
406
|
+
|
407
|
+
documents.flat_map do |doc|
|
408
|
+
# Extract business process information from document content
|
409
|
+
processes = []
|
410
|
+
processes << doc['title'] if doc['title']&.include?('Process')
|
411
|
+
processes << doc['title'] if doc['title']&.include?('Workflow')
|
412
|
+
processes << doc['title'] if doc['title']&.include?('Procedure')
|
413
|
+
processes << doc['content']&.truncate(200) if doc['content']
|
414
|
+
processes
|
415
|
+
end.compact.uniq
|
416
|
+
end
|
417
|
+
|
418
|
+
# Public method for Claude Terminal to access Markdown business context
|
419
|
+
def get_markdown_business_context(class_name)
|
420
|
+
return {} unless CodeHealer::ConfigManager.use_markdown_context?
|
421
|
+
|
422
|
+
begin
|
423
|
+
# Load from existing markdown business context
|
424
|
+
markdown_context = load_business_requirements_from_markdown
|
425
|
+
|
426
|
+
if markdown_context.any?
|
427
|
+
{
|
428
|
+
markdown_files: markdown_context['markdown_requirements'] || [],
|
429
|
+
business_rules: extract_business_rules_from_markdown(markdown_context),
|
430
|
+
requirements: extract_requirements_from_markdown(markdown_context)
|
431
|
+
}
|
432
|
+
end
|
433
|
+
rescue => e
|
434
|
+
puts "⚠️ Failed to get Markdown business context: #{e.message}"
|
435
|
+
end
|
436
|
+
|
437
|
+
{}
|
438
|
+
end
|
439
|
+
|
440
|
+
# Public method for Claude Terminal to extract business rules from markdown
|
441
|
+
def extract_business_rules_from_markdown(markdown_context)
|
442
|
+
return [] unless markdown_context['markdown_requirements']
|
443
|
+
|
444
|
+
markdown_context['markdown_requirements'].flat_map do |file_info|
|
445
|
+
content = file_info[:content]
|
446
|
+
rules = []
|
447
|
+
rules << content if content.include?('rule')
|
448
|
+
rules << content if content.include?('policy')
|
449
|
+
rules << content if content.include?('requirement')
|
450
|
+
rules
|
451
|
+
end.compact.uniq
|
452
|
+
end
|
453
|
+
|
454
|
+
# Public method for Claude Terminal to extract requirements from markdown
|
455
|
+
def extract_requirements_from_markdown(markdown_context)
|
456
|
+
return [] unless markdown_context['markdown_requirements']
|
457
|
+
|
458
|
+
markdown_context['markdown_requirements'].flat_map do |file_info|
|
459
|
+
content = file_info[:content]
|
460
|
+
requirements = []
|
461
|
+
requirements << content if content.include?('requirement')
|
462
|
+
requirements << content if content.include?('must')
|
463
|
+
requirements << content if content.include?('should')
|
464
|
+
requirements << content if content.include?('need')
|
465
|
+
requirements
|
466
|
+
end.compact.uniq
|
247
467
|
end
|
248
468
|
|
249
469
|
def get_evolution_history(class_name, method_name)
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'cgi'
|
3
|
+
|
1
4
|
module CodeHealer
|
2
5
|
# Tool for analyzing errors with context
|
3
6
|
class ErrorAnalysisTool < MCP::Tool
|
@@ -1522,9 +1525,14 @@ module CodeHealer
|
|
1522
1525
|
return { error: "Ticket ID required" } unless ticket_id
|
1523
1526
|
|
1524
1527
|
begin
|
1525
|
-
#
|
1526
|
-
if
|
1527
|
-
ticket_data =
|
1528
|
+
# Try real JIRA API first, fallback to simulation if it fails
|
1529
|
+
if jira_api_available?
|
1530
|
+
ticket_data = get_real_jira_ticket(ticket_id)
|
1531
|
+
|
1532
|
+
if ticket_data[:error]
|
1533
|
+
puts "⚠️ Real JIRA API failed, falling back to simulation: #{ticket_data[:error]}"
|
1534
|
+
ticket_data = simulate_jira_ticket(ticket_id)
|
1535
|
+
end
|
1528
1536
|
|
1529
1537
|
{
|
1530
1538
|
ticket_id: ticket_id,
|
@@ -1590,8 +1598,13 @@ module CodeHealer
|
|
1590
1598
|
return { error: "Project key required" } unless project_key
|
1591
1599
|
|
1592
1600
|
begin
|
1593
|
-
if
|
1594
|
-
project_data =
|
1601
|
+
if jira_api_available?
|
1602
|
+
project_data = get_real_jira_project(project_key)
|
1603
|
+
|
1604
|
+
if project_data[:error]
|
1605
|
+
puts "⚠️ Real JIRA API failed, falling back to simulation: #{project_data[:error]}"
|
1606
|
+
project_data = simulate_jira_project(project_key)
|
1607
|
+
end
|
1595
1608
|
|
1596
1609
|
{
|
1597
1610
|
project_key: project_key,
|
@@ -1845,6 +1858,378 @@ module CodeHealer
|
|
1845
1858
|
priority_distribution: { "High": 20, "Medium": 60, "Low": 20 }
|
1846
1859
|
}
|
1847
1860
|
end
|
1861
|
+
|
1862
|
+
# Real JIRA API integration methods
|
1863
|
+
def self.jira_api_available?
|
1864
|
+
# Check if JIRA configuration is available
|
1865
|
+
ENV['JIRA_URL'] && ENV['JIRA_USERNAME'] && ENV['JIRA_API_TOKEN']
|
1866
|
+
end
|
1867
|
+
|
1868
|
+
def self.jira_api_call(endpoint, method = :get, body = nil)
|
1869
|
+
return { error: "JIRA configuration not available" } unless jira_api_available?
|
1870
|
+
|
1871
|
+
begin
|
1872
|
+
url = "#{ENV['JIRA_URL']}/rest/api/2#{endpoint}"
|
1873
|
+
auth = { username: ENV['JIRA_USERNAME'], password: ENV['JIRA_API_TOKEN'] }
|
1874
|
+
|
1875
|
+
options = {
|
1876
|
+
basic_auth: auth,
|
1877
|
+
headers: { 'Content-Type' => 'application/json' }
|
1878
|
+
}
|
1879
|
+
|
1880
|
+
case method
|
1881
|
+
when :get
|
1882
|
+
response = HTTParty.get(url, options)
|
1883
|
+
when :post
|
1884
|
+
response = HTTParty.post(url, options.merge(body: body.to_json))
|
1885
|
+
when :put
|
1886
|
+
response = HTTParty.put(url, options.merge(body: body.to_json))
|
1887
|
+
else
|
1888
|
+
return { error: "Unsupported HTTP method: #{method}" }
|
1889
|
+
end
|
1890
|
+
|
1891
|
+
if response.success?
|
1892
|
+
JSON.parse(response.body)
|
1893
|
+
else
|
1894
|
+
{ error: "JIRA API error: #{response.code} - #{response.body}" }
|
1895
|
+
end
|
1896
|
+
rescue => e
|
1897
|
+
{ error: "JIRA API call failed: #{e.message}" }
|
1898
|
+
end
|
1899
|
+
end
|
1900
|
+
|
1901
|
+
def self.get_real_jira_ticket(ticket_id)
|
1902
|
+
endpoint = "/issue/#{ticket_id}"
|
1903
|
+
response = jira_api_call(endpoint)
|
1904
|
+
|
1905
|
+
return response if response['error']
|
1906
|
+
|
1907
|
+
# Transform Jira API response to our format
|
1908
|
+
{
|
1909
|
+
id: response['key'],
|
1910
|
+
summary: response['fields']['summary'],
|
1911
|
+
description: response['fields']['description'],
|
1912
|
+
status: response['fields']['status']['name'],
|
1913
|
+
priority: response['fields']['priority']['name'],
|
1914
|
+
assignee: response['fields']['assignee']&.dig('emailAddress'),
|
1915
|
+
reporter: response['fields']['reporter']&.dig('emailAddress'),
|
1916
|
+
created: response['fields']['created'],
|
1917
|
+
updated: response['fields']['updated'],
|
1918
|
+
issue_type: response['fields']['issuetype']['name'],
|
1919
|
+
project: response['fields']['project']['key'],
|
1920
|
+
components: response['fields']['components']&.map { |c| c['name'] } || [],
|
1921
|
+
labels: response['fields']['labels'] || [],
|
1922
|
+
comments: response['fields']['comment']&.dig('comments')&.map { |c|
|
1923
|
+
{ author: c['author']['emailAddress'], body: c['body'], created: c['created'] }
|
1924
|
+
} || [],
|
1925
|
+
attachments: response['fields']['attachment']&.map { |a| a['filename'] } || [],
|
1926
|
+
related_issues: [], # Jira doesn't provide this directly
|
1927
|
+
time_tracking: {
|
1928
|
+
original_estimate: response['fields']['timeoriginalestimate'],
|
1929
|
+
time_spent: response['fields']['timespent'],
|
1930
|
+
remaining_estimate: response['fields']['timeestimate']
|
1931
|
+
}
|
1932
|
+
}
|
1933
|
+
end
|
1934
|
+
|
1935
|
+
def self.get_real_jira_search(query, project_key, issue_type, status, assignee)
|
1936
|
+
jql_parts = []
|
1937
|
+
jql_parts << "project = #{project_key}" if project_key
|
1938
|
+
jql_parts << "issuetype = #{issue_type}" if issue_type
|
1939
|
+
jql_parts << "status = #{status}" if status
|
1940
|
+
jql_parts << "assignee = #{assignee}" if assignee
|
1941
|
+
jql_parts << "text ~ \"#{query}\"" if query
|
1942
|
+
|
1943
|
+
jql = jql_parts.join(" AND ")
|
1944
|
+
endpoint = "/search?jql=#{CGI.escape(jql)}&maxResults=10"
|
1945
|
+
|
1946
|
+
response = jira_api_call(endpoint)
|
1947
|
+
return [] if response['error']
|
1948
|
+
|
1949
|
+
response['issues']&.map do |issue|
|
1950
|
+
{
|
1951
|
+
id: issue['key'],
|
1952
|
+
summary: issue['fields']['summary'],
|
1953
|
+
status: issue['fields']['status']['name'],
|
1954
|
+
priority: issue['fields']['priority']['name'],
|
1955
|
+
assignee: issue['fields']['assignee']&.dig('emailAddress'),
|
1956
|
+
issue_type: issue['fields']['issuetype']['name'],
|
1957
|
+
created: issue['fields']['created'],
|
1958
|
+
updated: issue['fields']['updated']
|
1959
|
+
}
|
1960
|
+
end || []
|
1961
|
+
end
|
1962
|
+
|
1963
|
+
def self.get_real_jira_project(project_key)
|
1964
|
+
endpoint = "/project/#{project_key}"
|
1965
|
+
response = jira_api_call(endpoint)
|
1966
|
+
|
1967
|
+
return response if response['error']
|
1968
|
+
|
1969
|
+
{
|
1970
|
+
key: response['key'],
|
1971
|
+
name: response['name'],
|
1972
|
+
description: response['description'],
|
1973
|
+
lead: response['lead']['emailAddress'],
|
1974
|
+
url: "#{ENV['JIRA_URL']}/browse/#{project_key}",
|
1975
|
+
components: response['components']&.map { |c| c['name'] } || [],
|
1976
|
+
issue_types: response['issueTypes']&.map { |it| it['name'] } || [],
|
1977
|
+
statuses: response['statuses']&.map { |s| s['name'] } || [],
|
1978
|
+
versions: response['versions']&.map { |v| v['name'] } || [],
|
1979
|
+
permissions: response['permissions'] || []
|
1980
|
+
}
|
1981
|
+
end
|
1982
|
+
|
1983
|
+
# Fallback to simulation if real API fails
|
1984
|
+
def self.simulate_jira_available?
|
1985
|
+
# Check if JIRA configuration is available
|
1986
|
+
ENV['JIRA_URL'] && ENV['JIRA_USERNAME'] && ENV['JIRA_API_TOKEN']
|
1987
|
+
end
|
1988
|
+
end
|
1989
|
+
|
1990
|
+
# Tool for Confluence integration and business context retrieval
|
1991
|
+
class ConfluenceIntegrationTool < MCP::Tool
|
1992
|
+
description "Integrates with Confluence to retrieve PRDs, business requirements, and documentation for business context"
|
1993
|
+
input_schema(
|
1994
|
+
properties: {
|
1995
|
+
action: { type: "string", enum: ["search_documents", "get_document", "get_space_info", "search_prd"] },
|
1996
|
+
search_query: { type: "string" },
|
1997
|
+
space_key: { type: "string" },
|
1998
|
+
document_id: { type: "string" },
|
1999
|
+
server_context: { type: "object" }
|
2000
|
+
},
|
2001
|
+
required: ["action"]
|
2002
|
+
)
|
2003
|
+
annotations(
|
2004
|
+
title: "Confluence Integration Tool",
|
2005
|
+
read_only_hint: true,
|
2006
|
+
destructive_hint: false,
|
2007
|
+
idempotent_hint: true,
|
2008
|
+
open_world_hint: false
|
2009
|
+
)
|
2010
|
+
|
2011
|
+
def self.call(action:, search_query: nil, space_key: nil, document_id: nil, server_context:)
|
2012
|
+
case action
|
2013
|
+
when "search_documents"
|
2014
|
+
search_confluence_documents(search_query, space_key)
|
2015
|
+
when "get_document"
|
2016
|
+
get_confluence_document(document_id)
|
2017
|
+
when "get_space_info"
|
2018
|
+
get_confluence_space_info(space_key)
|
2019
|
+
when "search_prd"
|
2020
|
+
search_prd_documents(search_query, space_key)
|
2021
|
+
else
|
2022
|
+
MCP::Tool::Response.new([{ type: "text", text: { error: "Unknown action: #{action}" }.to_json }])
|
2023
|
+
end
|
2024
|
+
end
|
2025
|
+
|
2026
|
+
private
|
2027
|
+
|
2028
|
+
def self.search_confluence_documents(query, space_key)
|
2029
|
+
if confluence_api_available?
|
2030
|
+
get_real_confluence_search(query, space_key)
|
2031
|
+
else
|
2032
|
+
simulate_confluence_search(query, space_key)
|
2033
|
+
end
|
2034
|
+
end
|
2035
|
+
|
2036
|
+
def self.get_confluence_document(document_id)
|
2037
|
+
if confluence_api_available?
|
2038
|
+
get_real_confluence_document(document_id)
|
2039
|
+
else
|
2040
|
+
simulate_confluence_document(document_id)
|
2041
|
+
end
|
2042
|
+
end
|
2043
|
+
|
2044
|
+
def self.get_confluence_space_info(space_key)
|
2045
|
+
if confluence_api_available?
|
2046
|
+
get_real_confluence_space_info(space_key)
|
2047
|
+
else
|
2048
|
+
simulate_confluence_space_info(space_key)
|
2049
|
+
end
|
2050
|
+
end
|
2051
|
+
|
2052
|
+
def self.search_prd_documents(query, space_key)
|
2053
|
+
if confluence_api_available?
|
2054
|
+
get_real_confluence_prd_search(query, space_key)
|
2055
|
+
else
|
2056
|
+
simulate_confluence_prd_search(query, space_key)
|
2057
|
+
end
|
2058
|
+
end
|
2059
|
+
|
2060
|
+
# Real Confluence API calls
|
2061
|
+
def self.confluence_api_available?
|
2062
|
+
ENV['CONFLUENCE_URL'] && ENV['CONFLUENCE_USERNAME'] && ENV['CONFLUENCE_API_TOKEN']
|
2063
|
+
end
|
2064
|
+
|
2065
|
+
def self.confluence_api_call(endpoint, params = {})
|
2066
|
+
return nil unless confluence_api_available?
|
2067
|
+
|
2068
|
+
url = "#{ENV['CONFLUENCE_URL']}/rest/api#{endpoint}"
|
2069
|
+
auth = { username: ENV['CONFLUENCE_USERNAME'], password: ENV['CONFLUENCE_API_TOKEN'] }
|
2070
|
+
|
2071
|
+
begin
|
2072
|
+
response = HTTParty.get(url, basic_auth: auth, query: params)
|
2073
|
+
response.success? ? response.parsed_response : nil
|
2074
|
+
rescue => e
|
2075
|
+
puts "⚠️ Confluence API call failed: #{e.message}"
|
2076
|
+
nil
|
2077
|
+
end
|
2078
|
+
end
|
2079
|
+
|
2080
|
+
def self.get_real_confluence_search(query, space_key)
|
2081
|
+
params = {
|
2082
|
+
cql: "space = #{space_key} AND text ~ \"#{query}\"",
|
2083
|
+
limit: 10,
|
2084
|
+
expand: "content"
|
2085
|
+
}
|
2086
|
+
|
2087
|
+
data = confluence_api_call('/content/search', params)
|
2088
|
+
return { error: "Failed to search Confluence" } unless data
|
2089
|
+
|
2090
|
+
documents = data['results']&.map do |result|
|
2091
|
+
{
|
2092
|
+
id: result['id'],
|
2093
|
+
title: result['title'],
|
2094
|
+
type: result['type'],
|
2095
|
+
space_key: result['space']['key'],
|
2096
|
+
url: "#{ENV['CONFLUENCE_URL']}/wiki#{result['_links']['webui']}",
|
2097
|
+
content: result['excerpt'] || result['title'],
|
2098
|
+
labels: result['metadata']&.dig('labels', 'results')&.map { |l| l['name'] } || []
|
2099
|
+
}
|
2100
|
+
end || []
|
2101
|
+
|
2102
|
+
MCP::Tool::Response.new([{ type: "text", text: { documents: documents }.to_json }])
|
2103
|
+
end
|
2104
|
+
|
2105
|
+
def self.get_real_confluence_document(document_id)
|
2106
|
+
data = confluence_api_call("/content/#{document_id}")
|
2107
|
+
return { error: "Failed to get document" } unless data
|
2108
|
+
|
2109
|
+
document = {
|
2110
|
+
id: data['id'],
|
2111
|
+
title: data['title'],
|
2112
|
+
type: data['type'],
|
2113
|
+
space_key: data['space']['key'],
|
2114
|
+
url: "#{ENV['CONFLUENCE_URL']}/wiki#{data['_links']['webui']}",
|
2115
|
+
content: data['body']&.dig('storage', 'value') || data['title'],
|
2116
|
+
labels: data['metadata']&.dig('labels', 'results')&.map { |l| l['name'] } || []
|
2117
|
+
}
|
2118
|
+
|
2119
|
+
MCP::Tool::Response.new([{ type: "text", text: { document: document }.to_json }])
|
2120
|
+
end
|
2121
|
+
|
2122
|
+
def self.get_real_confluence_space_info(space_key)
|
2123
|
+
data = confluence_api_call("/space/#{space_key}")
|
2124
|
+
return { error: "Failed to get space info" } unless data
|
2125
|
+
|
2126
|
+
space_info = {
|
2127
|
+
key: data['key'],
|
2128
|
+
name: data['name'],
|
2129
|
+
type: data['type'],
|
2130
|
+
description: data['description'],
|
2131
|
+
url: "#{ENV['CONFLUENCE_URL']}/wiki/spaces/#{space_key}",
|
2132
|
+
permissions: data['permissions'] || []
|
2133
|
+
}
|
2134
|
+
|
2135
|
+
MCP::Tool::Response.new([{ type: "text", text: { space: space_info }.to_json }])
|
2136
|
+
end
|
2137
|
+
|
2138
|
+
def self.get_real_confluence_prd_search(query, space_key)
|
2139
|
+
params = {
|
2140
|
+
cql: "space = #{space_key} AND (text ~ \"PRD\" OR text ~ \"Product Requirements\" OR text ~ \"#{query}\")",
|
2141
|
+
limit: 5,
|
2142
|
+
expand: "content"
|
2143
|
+
}
|
2144
|
+
|
2145
|
+
data = confluence_api_call('/content/search', params)
|
2146
|
+
return { error: "Failed to search PRD documents" } unless data
|
2147
|
+
|
2148
|
+
prd_documents = data['results']&.map do |result|
|
2149
|
+
{
|
2150
|
+
id: result['id'],
|
2151
|
+
title: result['title'],
|
2152
|
+
type: result['type'],
|
2153
|
+
space_key: result['space']['key'],
|
2154
|
+
url: "#{ENV['CONFLUENCE_URL']}/wiki#{result['_links']['webui']}",
|
2155
|
+
content: result['excerpt'] || result['title'],
|
2156
|
+
is_prd: result['title'].include?('PRD') || result['title'].include?('Product Requirements'),
|
2157
|
+
labels: result['metadata']&.dig('labels', 'results')&.map { |l| l['name'] } || []
|
2158
|
+
}
|
2159
|
+
end || []
|
2160
|
+
|
2161
|
+
MCP::Tool::Response.new([{ type: "text", text: { documents: prd_documents }.to_json }])
|
2162
|
+
end
|
2163
|
+
|
2164
|
+
# Simulation methods for when Confluence API is not available
|
2165
|
+
def self.simulate_confluence_search(query, space_key)
|
2166
|
+
documents = [
|
2167
|
+
{
|
2168
|
+
id: "sim-1",
|
2169
|
+
title: "Business Rules for #{space_key}",
|
2170
|
+
type: "page",
|
2171
|
+
space_key: space_key,
|
2172
|
+
url: "https://confluence.example.com/wiki/spaces/#{space_key}/pages/sim-1",
|
2173
|
+
content: "Simulated business rules document for #{space_key} project",
|
2174
|
+
labels: ["business-rules", "requirements"]
|
2175
|
+
},
|
2176
|
+
{
|
2177
|
+
id: "sim-2",
|
2178
|
+
title: "Process Documentation",
|
2179
|
+
type: "page",
|
2180
|
+
space_key: space_key,
|
2181
|
+
url: "https://confluence.example.com/wiki/spaces/#{space_key}/pages/sim-2",
|
2182
|
+
content: "Simulated process documentation for #{space_key}",
|
2183
|
+
labels: ["process", "workflow"]
|
2184
|
+
}
|
2185
|
+
]
|
2186
|
+
|
2187
|
+
MCP::Tool::Response.new([{ type: "text", text: { documents: documents }.to_json }])
|
2188
|
+
end
|
2189
|
+
|
2190
|
+
def self.simulate_confluence_document(document_id)
|
2191
|
+
document = {
|
2192
|
+
id: document_id,
|
2193
|
+
title: "Simulated Confluence Document",
|
2194
|
+
type: "page",
|
2195
|
+
space_key: "DGTL",
|
2196
|
+
url: "https://confluence.example.com/wiki/spaces/DGTL/pages/#{document_id}",
|
2197
|
+
content: "This is a simulated Confluence document content for testing purposes.",
|
2198
|
+
labels: ["simulated", "test"]
|
2199
|
+
}
|
2200
|
+
|
2201
|
+
MCP::Tool::Response.new([{ type: "text", text: { document: document }.to_json }])
|
2202
|
+
end
|
2203
|
+
|
2204
|
+
def self.simulate_confluence_space_info(space_key)
|
2205
|
+
space_info = {
|
2206
|
+
key: space_key,
|
2207
|
+
name: "#{space_key} Project Space",
|
2208
|
+
type: "global",
|
2209
|
+
description: "Simulated Confluence space for #{space_key} project",
|
2210
|
+
url: "https://confluence.example.com/wiki/spaces/#{space_key}",
|
2211
|
+
permissions: ["view", "edit", "create"]
|
2212
|
+
}
|
2213
|
+
|
2214
|
+
MCP::Tool::Response.new([{ type: "text", text: { space: space_info }.to_json }])
|
2215
|
+
end
|
2216
|
+
|
2217
|
+
def self.simulate_confluence_prd_search(query, space_key)
|
2218
|
+
prd_documents = [
|
2219
|
+
{
|
2220
|
+
id: "prd-1",
|
2221
|
+
title: "Product Requirements Document - #{space_key}",
|
2222
|
+
type: "page",
|
2223
|
+
space_key: space_key,
|
2224
|
+
url: "https://confluence.example.com/wiki/spaces/#{space_key}/pages/prd-1",
|
2225
|
+
content: "Simulated PRD for #{space_key} project with requirements and business rules",
|
2226
|
+
is_prd: true,
|
2227
|
+
labels: ["PRD", "requirements", "product"]
|
2228
|
+
}
|
2229
|
+
]
|
2230
|
+
|
2231
|
+
MCP::Tool::Response.new([{ type: "text", text: { documents: prd_documents }.to_json }])
|
2232
|
+
end
|
1848
2233
|
end
|
1849
2234
|
|
1850
2235
|
# Tool for intelligent context analysis and runtime decision making
|
data/lib/code_healer/setup.rb
CHANGED
@@ -252,6 +252,36 @@ github_token = ask_for_input("Enter your GitHub personal access token (or press
|
|
252
252
|
github_repo = ask_for_input("Enter your GitHub repository (username/repo or full URL):")
|
253
253
|
github_repo_url = normalize_repository_url(github_repo, github_token)
|
254
254
|
|
255
|
+
# Jira Configuration
|
256
|
+
puts
|
257
|
+
puts "🎫 Jira Configuration:"
|
258
|
+
puts "Configure Jira integration for business context during healing operations."
|
259
|
+
puts "Get your API token at: https://id.atlassian.com/manage-profile/security/api-tokens"
|
260
|
+
puts
|
261
|
+
|
262
|
+
enable_jira = ask_for_yes_no("Enable Jira integration for business context?", default: false)
|
263
|
+
|
264
|
+
if enable_jira
|
265
|
+
jira_url = ask_for_input("Enter your Jira instance URL (e.g., https://your-company.atlassian.net):")
|
266
|
+
jira_username = ask_for_input("Enter your Jira username/email:")
|
267
|
+
jira_api_token = ask_for_input("Enter your Jira API token:")
|
268
|
+
jira_project_key = ask_for_input("Enter your default Jira project key (e.g., DGTL):")
|
269
|
+
|
270
|
+
# Validate Jira configuration
|
271
|
+
puts "🔍 Validating Jira configuration..."
|
272
|
+
if jira_url && jira_username && jira_api_token && jira_project_key
|
273
|
+
puts "✅ Jira configuration provided"
|
274
|
+
else
|
275
|
+
puts "⚠️ Incomplete Jira configuration - integration will be disabled"
|
276
|
+
enable_jira = false
|
277
|
+
end
|
278
|
+
else
|
279
|
+
jira_url = ""
|
280
|
+
jira_username = ""
|
281
|
+
jira_api_token = ""
|
282
|
+
jira_project_key = ""
|
283
|
+
end
|
284
|
+
|
255
285
|
# Git Branch Configuration
|
256
286
|
puts
|
257
287
|
puts "🌿 Git Branch Configuration:"
|
@@ -307,6 +337,60 @@ puts
|
|
307
337
|
puts "💼 Business Context Setup:"
|
308
338
|
create_business_context = ask_for_yes_no("Would you like to create a business context file?", default: true)
|
309
339
|
|
340
|
+
# Business Context Strategy Configurationy
|
341
|
+
puts
|
342
|
+
puts "🔍 Business Context Strategy Configuration:"
|
343
|
+
puts "Choose how CodeHealer should get business context:"
|
344
|
+
puts "1. Jira MCP (Claude Terminal uses its own Jira MCP)"
|
345
|
+
puts "2. Markdown files (docs/business_rules.md)"
|
346
|
+
puts "3. Hybrid (both Jira MCP and Markdown)"
|
347
|
+
puts
|
348
|
+
|
349
|
+
business_context_strategy = ask_for_input("Enter business context strategy (1/2/3 or jira_mcp/markdown/hybrid):", default: "jira_mcp")
|
350
|
+
business_context_strategy = case business_context_strategy.downcase
|
351
|
+
when "1", "jira_mcp"
|
352
|
+
"jira_mcp"
|
353
|
+
when "2", "markdown"
|
354
|
+
"markdown"
|
355
|
+
when "3", "hybrid"
|
356
|
+
"hybrid"
|
357
|
+
else
|
358
|
+
"jira_mcp"
|
359
|
+
end
|
360
|
+
|
361
|
+
# Configure Confluence if selected
|
362
|
+
enable_confluence = false
|
363
|
+
confluence_url = ""
|
364
|
+
confluence_username = ""
|
365
|
+
confluence_api_token = ""
|
366
|
+
confluence_space_key = ""
|
367
|
+
|
368
|
+
if business_context_source == "confluence" || business_context_source == "hybrid"
|
369
|
+
puts
|
370
|
+
puts "📚 Confluence Configuration:"
|
371
|
+
puts "Configure Confluence integration for PRDs and documentation."
|
372
|
+
puts "Get your API token at: https://id.atlassian.com/manage-profile/security/api-tokens"
|
373
|
+
puts
|
374
|
+
|
375
|
+
enable_confluence = ask_for_yes_no("Enable Confluence integration for business context?", default: false)
|
376
|
+
|
377
|
+
if enable_confluence
|
378
|
+
confluence_url = ask_for_input("Enter your Confluence instance URL (e.g., https://your-company.atlassian.net/wiki):")
|
379
|
+
confluence_username = ask_for_input("Enter your Confluence username/email:")
|
380
|
+
confluence_api_token = ask_for_input("Enter your Confluence API token:")
|
381
|
+
confluence_space_key = ask_for_input("Enter your Confluence space key (e.g., DGTL):")
|
382
|
+
|
383
|
+
# Validate Confluence configuration
|
384
|
+
puts "🔍 Validating Confluence configuration..."
|
385
|
+
if confluence_url && confluence_username && confluence_api_token && confluence_space_key
|
386
|
+
puts "✅ Confluence configuration provided"
|
387
|
+
else
|
388
|
+
puts "⚠️ Incomplete Confluence configuration - integration will be disabled"
|
389
|
+
enable_confluence = false
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
310
394
|
# Evolution Strategy Configuration
|
311
395
|
puts
|
312
396
|
puts "🧠 Evolution Strategy Configuration:"
|
@@ -374,6 +458,18 @@ puts
|
|
374
458
|
GITHUB_TOKEN=#{github_token}
|
375
459
|
GITHUB_REPOSITORY=#{github_repo}
|
376
460
|
|
461
|
+
# Jira Configuration
|
462
|
+
JIRA_URL=#{jira_url}
|
463
|
+
JIRA_USERNAME=#{jira_username}
|
464
|
+
JIRA_API_TOKEN=#{jira_api_token}
|
465
|
+
JIRA_PROJECT_KEY=#{jira_project_key}
|
466
|
+
|
467
|
+
# Confluence Configuration
|
468
|
+
CONFLUENCE_URL=#{confluence_url}
|
469
|
+
CONFLUENCE_USERNAME=#{confluence_username}
|
470
|
+
CONFLUENCE_API_TOKEN=#{confluence_api_token}
|
471
|
+
CONFLUENCE_SPACE_KEY=#{confluence_space_key}
|
472
|
+
|
377
473
|
# Optional: Redis Configuration
|
378
474
|
REDIS_URL=redis://localhost:6379/0
|
379
475
|
ENV
|
@@ -436,8 +532,36 @@ create_file_with_content('.env', env_content, dry_run: options[:dry_run])
|
|
436
532
|
# Business Context Configuration
|
437
533
|
business_context:
|
438
534
|
enabled: true
|
439
|
-
|
440
|
-
|
535
|
+
strategy: "#{business_context_strategy}"
|
536
|
+
|
537
|
+
# Jira MCP Configuration
|
538
|
+
jira_mcp:
|
539
|
+
enabled: #{business_context_strategy == 'jira_mcp' || business_context_strategy == 'hybrid'}
|
540
|
+
project_key: "#{jira_project_key}"
|
541
|
+
search_tickets_on_error: true
|
542
|
+
include_business_rules: true
|
543
|
+
system_prompt: |
|
544
|
+
When fixing code, ALWAYS check Jira MCP for business context:
|
545
|
+
1. Search for tickets about the class/method you're fixing
|
546
|
+
2. Use Jira requirements to ensure your fix follows business rules
|
547
|
+
3. Reference specific Jira tickets in your explanation
|
548
|
+
4. Make sure fixes align with business requirements
|
549
|
+
|
550
|
+
# Markdown Configuration
|
551
|
+
markdown:
|
552
|
+
enabled: #{business_context_strategy == 'markdown' || business_context_strategy == 'hybrid'}
|
553
|
+
search_paths:
|
554
|
+
- "docs/business_rules.md"
|
555
|
+
- "docs/requirements.md"
|
556
|
+
- "business_requirements/"
|
557
|
+
include_patterns:
|
558
|
+
- "*.md"
|
559
|
+
- "*.txt"
|
560
|
+
|
561
|
+
# Hybrid Configuration
|
562
|
+
hybrid:
|
563
|
+
priority: ["jira_mcp", "markdown"]
|
564
|
+
combine_results: true
|
441
565
|
|
442
566
|
# OpenAI API configuration
|
443
567
|
api:
|
@@ -462,6 +586,16 @@ create_file_with_content('.env', env_content, dry_run: options[:dry_run])
|
|
462
586
|
auto_create: true
|
463
587
|
labels:
|
464
588
|
- "auto-fix"
|
589
|
+
|
590
|
+
# Jira Integration Configuration
|
591
|
+
jira:
|
592
|
+
enabled: #{enable_jira}
|
593
|
+
url: "#{jira_url}"
|
594
|
+
username: "#{jira_username}"
|
595
|
+
project_key: "#{jira_project_key}"
|
596
|
+
business_context_enabled: #{enable_jira}
|
597
|
+
search_tickets_on_error: true
|
598
|
+
include_ticket_details: true
|
465
599
|
- "self-evolving"
|
466
600
|
- "bug-fix"
|
467
601
|
|
@@ -590,6 +724,11 @@ puts " - All features are pre-configured and ready to use"
|
|
590
724
|
puts "🌍 Environment Variables:"
|
591
725
|
puts " - Add 'gem \"dotenv-rails\"' to your Gemfile for automatic .env loading"
|
592
726
|
puts " - Or export variables manually: export GITHUB_TOKEN=your_token"
|
727
|
+
if enable_jira
|
728
|
+
puts " - Jira integration: export JIRA_URL=#{jira_url}"
|
729
|
+
puts " - Jira credentials: export JIRA_USERNAME=#{jira_username}"
|
730
|
+
puts " - Jira project: export JIRA_PROJECT_KEY=#{jira_project_key}"
|
731
|
+
end
|
593
732
|
puts " - Or load .env file in your application.rb: load '.env' if File.exist?('.env')"
|
594
733
|
puts
|
595
734
|
puts "CodeHealer will now automatically detect and heal errors in your application!"
|
data/lib/code_healer/version.rb
CHANGED
data/lib/code_healer.rb
CHANGED
@@ -8,12 +8,13 @@ require 'openai'
|
|
8
8
|
require 'sidekiq'
|
9
9
|
require 'git'
|
10
10
|
require 'octokit'
|
11
|
+
require 'open3'
|
11
12
|
|
12
13
|
# CodeHealer - AI-Powered Code Healing and Self-Repair System
|
13
14
|
module CodeHealer
|
14
15
|
class Error < StandardError; end
|
15
16
|
|
16
|
-
|
17
|
+
|
17
18
|
end
|
18
19
|
|
19
20
|
# Autoload all the main classes
|
@@ -88,5 +89,7 @@ if defined?(Rails)
|
|
88
89
|
puts "⚠️ Claude preload failed: #{e.message}"
|
89
90
|
end
|
90
91
|
end
|
92
|
+
|
93
|
+
|
91
94
|
end
|
92
95
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: code_healer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.20
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Deepan Kumar
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-08-
|
11
|
+
date: 2025-08-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -160,6 +160,26 @@ dependencies:
|
|
160
160
|
- - ">="
|
161
161
|
- !ruby/object:Gem::Version
|
162
162
|
version: 6.0.0
|
163
|
+
- !ruby/object:Gem::Dependency
|
164
|
+
name: httparty
|
165
|
+
requirement: !ruby/object:Gem::Requirement
|
166
|
+
requirements:
|
167
|
+
- - "~>"
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: 0.21.0
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: 0.21.0
|
173
|
+
type: :runtime
|
174
|
+
prerelease: false
|
175
|
+
version_requirements: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - "~>"
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: 0.21.0
|
180
|
+
- - ">="
|
181
|
+
- !ruby/object:Gem::Version
|
182
|
+
version: 0.21.0
|
163
183
|
- !ruby/object:Gem::Dependency
|
164
184
|
name: bundler
|
165
185
|
requirement: !ruby/object:Gem::Requirement
|
@@ -348,6 +368,7 @@ files:
|
|
348
368
|
- DASHBOARD_README.md
|
349
369
|
- GEM_SUMMARY.md
|
350
370
|
- README.md
|
371
|
+
- bin/start_claude_session
|
351
372
|
- code_healer.gemspec
|
352
373
|
- config/code_healer.yml.example
|
353
374
|
- config/routes.rb
|