code_healer 0.1.18 → 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.
@@ -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
- # Simulate JIRA API call - in production, this would use actual JIRA REST API
1526
- if simulate_jira_available?
1527
- ticket_data = simulate_jira_ticket(ticket_id)
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 simulate_jira_available?
1594
- project_data = simulate_jira_project(project_key)
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
@@ -27,7 +27,7 @@ module CodeHealer
27
27
  puts "Creating pull request for branch: #{branch_name}"
28
28
 
29
29
  # Get target branch from configuration
30
- target_branch = ConfigManager.pr_target_branch || 'main'
30
+ target_branch = ConfigManager.pr_target_branch
31
31
  puts "📋 Target branch for PR: #{target_branch}"
32
32
 
33
33
  pr = client.create_pull_request(
@@ -252,11 +252,51 @@ 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:"
258
288
  branch_prefix = ask_for_input("Enter branch prefix for healing branches (default: evolve):", default: "evolve")
259
- pr_target_branch = ask_for_input("Enter target branch for pull requests (default: main):", default: "main")
289
+
290
+ # Detect the actual default branch from git
291
+ default_branch = "main"
292
+ if system("git rev-parse --verify master >/dev/null 2>&1")
293
+ default_branch = "master"
294
+ elsif system("git rev-parse --verify main >/dev/null 2>&1")
295
+ default_branch = "main"
296
+ end
297
+
298
+ puts "🔍 Detected default branch: #{default_branch}"
299
+ pr_target_branch = ask_for_input("Enter target branch for pull requests (default: #{default_branch}):", default: default_branch)
260
300
 
261
301
  # Code Heal Directory Configuration
262
302
  puts
@@ -297,6 +337,60 @@ puts
297
337
  puts "💼 Business Context Setup:"
298
338
  create_business_context = ask_for_yes_no("Would you like to create a business context file?", default: true)
299
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
+
300
394
  # Evolution Strategy Configuration
301
395
  puts
302
396
  puts "🧠 Evolution Strategy Configuration:"
@@ -364,6 +458,18 @@ puts
364
458
  GITHUB_TOKEN=#{github_token}
365
459
  GITHUB_REPOSITORY=#{github_repo}
366
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
+
367
473
  # Optional: Redis Configuration
368
474
  REDIS_URL=redis://localhost:6379/0
369
475
  ENV
@@ -426,8 +532,36 @@ create_file_with_content('.env', env_content, dry_run: options[:dry_run])
426
532
  # Business Context Configuration
427
533
  business_context:
428
534
  enabled: true
429
- sources:
430
- - "docs/business_rules.md"
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
431
565
 
432
566
  # OpenAI API configuration
433
567
  api:
@@ -442,7 +576,9 @@ create_file_with_content('.env', env_content, dry_run: options[:dry_run])
442
576
  auto_push: true
443
577
  branch_prefix: "#{branch_prefix}"
444
578
  commit_message_template: 'Fix {{class_name}}\#\#{{method_name}}: {{error_type}}'
445
- pr_target_branch: "#{pr_target_branch}"
579
+
580
+ # Pull Request target branch (for backward compatibility)
581
+ pr_target_branch: "#{pr_target_branch}"
446
582
 
447
583
  # Pull Request Configuration
448
584
  pull_request:
@@ -450,6 +586,16 @@ create_file_with_content('.env', env_content, dry_run: options[:dry_run])
450
586
  auto_create: true
451
587
  labels:
452
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
453
599
  - "self-evolving"
454
600
  - "bug-fix"
455
601
 
@@ -578,6 +724,11 @@ puts " - All features are pre-configured and ready to use"
578
724
  puts "🌍 Environment Variables:"
579
725
  puts " - Add 'gem \"dotenv-rails\"' to your Gemfile for automatic .env loading"
580
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
581
732
  puts " - Or load .env file in your application.rb: load '.env' if File.exist?('.env')"
582
733
  puts
583
734
  puts "CodeHealer will now automatically detect and heal errors in your application!"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CodeHealer
4
- VERSION = "0.1.18"
4
+ VERSION = "0.1.20"
5
5
  end
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
- # Your code goes here...
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