code_healer 0.1.12 → 0.1.15

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.
@@ -0,0 +1,116 @@
1
+ module CodeHealer
2
+ class HealingMetric < ActiveRecord::Base
3
+ self.table_name = 'healing_metrics'
4
+
5
+ # Validations
6
+ validates :healing_id, presence: true, uniqueness: true
7
+ validates :class_name, presence: true
8
+ validates :method_name, presence: true
9
+ validates :error_class, presence: true
10
+
11
+ # Scopes for common queries
12
+ scope :successful, -> { where(healing_successful: true) }
13
+ scope :failed, -> { where(healing_successful: false) }
14
+ scope :recent, ->(days = 30) { where('created_at >= ?', days.days.ago) }
15
+ scope :by_evolution_method, ->(method) { where(evolution_method: method) }
16
+ scope :by_ai_provider, ->(provider) { where(ai_provider: provider) }
17
+ scope :by_class, ->(class_name) { where(class_name: class_name) }
18
+
19
+ # Class methods for dashboard metrics
20
+ class << self
21
+ def total_healings
22
+ count
23
+ end
24
+
25
+ def success_rate
26
+ return 0 if count.zero?
27
+ (successful.count.to_f / count * 100).round(2)
28
+ end
29
+
30
+ def healings_today
31
+ where('created_at >= ?', Date.current.beginning_of_day).count
32
+ end
33
+
34
+ def healings_this_week
35
+ where('created_at >= ?', Date.current.beginning_of_week).count
36
+ end
37
+
38
+ def healings_this_month
39
+ where('created_at >= ?', Date.current.beginning_of_month).count
40
+ end
41
+
42
+ def average_resolution_time
43
+ successful.where.not(total_duration_ms: nil).average(:total_duration_ms)&.round(2)
44
+ end
45
+
46
+ def evolution_method_distribution
47
+ group(:evolution_method).count
48
+ end
49
+
50
+ def ai_provider_distribution
51
+ group(:ai_provider).count
52
+ end
53
+
54
+ def top_error_classes(limit = 10)
55
+ group(:error_class).order('count_all DESC').limit(limit).count
56
+ end
57
+
58
+ def top_classes_healed(limit = 10)
59
+ group(:class_name).order('count_all DESC').limit(limit).count
60
+ end
61
+
62
+ def daily_healing_trend(days = 30)
63
+ # Use Rails date methods instead of raw SQL
64
+ start_date = days.days.ago.to_date
65
+ end_date = Date.current
66
+
67
+ trend_data = {}
68
+ (start_date..end_date).each do |date|
69
+ count = where('DATE(created_at) = ?', date).count
70
+ trend_data[date.strftime('%Y-%m-%d')] = count
71
+ end
72
+ trend_data
73
+ end
74
+
75
+ def hourly_healing_distribution
76
+ # Use Rails to group by hour without raw SQL
77
+ distribution = {}
78
+ (0..23).each do |hour|
79
+ start_time = Time.current.beginning_of_day + hour.hours
80
+ end_time = start_time + 1.hour
81
+ count = where(created_at: start_time..end_time).count
82
+ distribution[hour.to_s.rjust(2, '0')] = count
83
+ end
84
+ distribution
85
+ end
86
+ end
87
+
88
+ # Instance methods
89
+ def duration_seconds
90
+ return nil unless total_duration_ms
91
+ (total_duration_ms / 1000.0).round(2)
92
+ end
93
+
94
+ def ai_processing_seconds
95
+ return nil unless ai_processing_time_ms
96
+ (ai_processing_time_ms / 1000.0).round(2)
97
+ end
98
+
99
+ def git_operations_seconds
100
+ return nil unless git_operations_time_ms
101
+ (git_operations_time_ms / 1000.0).round(2)
102
+ end
103
+
104
+ def success_status
105
+ healing_successful ? '✅ Success' : '❌ Failed'
106
+ end
107
+
108
+ def evolution_method_display
109
+ evolution_method&.titleize || 'Unknown'
110
+ end
111
+
112
+ def ai_provider_display
113
+ ai_provider&.titleize || 'Unknown'
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,20 @@
1
+ # CodeHealer Dashboard Routes
2
+ Rails.application.routes.draw do
3
+ namespace :code_healer do
4
+ # Dashboard
5
+ get '/dashboard', to: 'dashboard#index'
6
+ get '/dashboard/metrics', to: 'dashboard#metrics'
7
+ get '/dashboard/trends', to: 'dashboard#trends'
8
+ get '/dashboard/performance', to: 'dashboard#performance'
9
+ get '/dashboard/healing/:healing_id', to: 'dashboard#healing_details'
10
+
11
+ # API endpoints (JSON only)
12
+ namespace :api do
13
+ get '/dashboard/summary', to: 'dashboard#summary'
14
+ get '/dashboard/metrics', to: 'dashboard#metrics'
15
+ get '/dashboard/trends', to: 'dashboard#trends'
16
+ get '/dashboard/performance', to: 'dashboard#performance'
17
+ get '/dashboard/healing/:healing_id', to: 'dashboard#healing_details'
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,135 @@
1
+ module CodeHealer
2
+ class MetricsCollector
3
+ def self.track_healing_start(healing_id, class_name, method_name, error_class, error_message, file_path)
4
+ metric = HealingMetric.find_or_initialize_by(healing_id: healing_id)
5
+
6
+ metric.assign_attributes(
7
+ class_name: class_name,
8
+ method_name: method_name,
9
+ error_class: error_class,
10
+ error_message: error_message,
11
+ file_path: file_path,
12
+ healing_started_at: Time.current
13
+ )
14
+
15
+ metric.save!
16
+ metric
17
+ end
18
+
19
+ def self.track_ai_processing(healing_id, evolution_method, ai_provider, ai_response, tokens_used = nil, cost = nil)
20
+ metric = HealingMetric.find_by(healing_id: healing_id)
21
+ return unless metric
22
+
23
+ metric.update!(
24
+ evolution_method: evolution_method,
25
+ ai_provider: ai_provider,
26
+ ai_response: ai_response,
27
+ ai_tokens_used: tokens_used,
28
+ ai_cost: cost,
29
+ ai_success: true
30
+ )
31
+ end
32
+
33
+ def self.track_ai_failure(healing_id, evolution_method, ai_provider, failure_reason)
34
+ metric = HealingMetric.find_by(healing_id: healing_id)
35
+ return unless metric
36
+
37
+ metric.update!(
38
+ evolution_method: evolution_method,
39
+ ai_provider: ai_provider,
40
+ ai_success: false,
41
+ failure_reason: failure_reason
42
+ )
43
+ end
44
+
45
+ def self.track_workspace_creation(healing_id, workspace_path)
46
+ metric = HealingMetric.find_by(healing_id: healing_id)
47
+ return unless metric
48
+
49
+ metric.update!(workspace_path: workspace_path)
50
+ end
51
+
52
+ def self.track_git_operations(healing_id, healing_branch, pull_request_url, pr_created)
53
+ metric = HealingMetric.find_by(healing_id: healing_id)
54
+ return unless metric
55
+
56
+ metric.update!(
57
+ healing_branch: healing_branch,
58
+ pull_request_url: pull_request_url,
59
+ pr_created: pr_created
60
+ )
61
+ end
62
+
63
+ def self.track_healing_completion(healing_id, success, tests_passed, syntax_valid, failure_reason = nil)
64
+ metric = HealingMetric.find_by(healing_id: healing_id)
65
+ return unless metric
66
+
67
+ # Calculate timing
68
+ total_duration = if metric.healing_started_at
69
+ ((Time.current - metric.healing_started_at) * 1000).round
70
+ else
71
+ nil
72
+ end
73
+
74
+ metric.update!(
75
+ healing_completed_at: Time.current,
76
+ total_duration_ms: total_duration,
77
+ healing_successful: success,
78
+ tests_passed: tests_passed,
79
+ syntax_valid: syntax_valid,
80
+ failure_reason: failure_reason
81
+ )
82
+ end
83
+
84
+ def self.track_timing(healing_id, ai_processing_time_ms, git_operations_time_ms)
85
+ metric = HealingMetric.find_by(healing_id: healing_id)
86
+ return unless metric
87
+
88
+ metric.update!(
89
+ ai_processing_time_ms: ai_processing_time_ms,
90
+ git_operations_time_ms: git_operations_time_ms
91
+ )
92
+ end
93
+
94
+ def self.track_business_context(healing_id, business_context, jira_issue_id = nil, confluence_page_id = nil)
95
+ metric = HealingMetric.find_by(healing_id: healing_id)
96
+ return unless metric
97
+
98
+ metric.update!(
99
+ business_context_used: business_context,
100
+ jira_issue_id: jira_issue_id,
101
+ confluence_page_id: confluence_page_id
102
+ )
103
+ end
104
+
105
+ def self.track_error_occurrence(healing_id, error_occurred_at)
106
+ metric = HealingMetric.find_by(healing_id: healing_id)
107
+ return unless metric
108
+
109
+ metric.update!(error_occurred_at: error_occurred_at)
110
+ end
111
+
112
+ # Generate unique healing ID
113
+ def self.generate_healing_id
114
+ "healing_#{Time.current.to_i}_#{SecureRandom.hex(8)}"
115
+ end
116
+
117
+ # Get dashboard summary
118
+ def self.dashboard_summary
119
+ {
120
+ total_healings: HealingMetric.total_healings,
121
+ success_rate: HealingMetric.success_rate,
122
+ healings_today: HealingMetric.healings_today,
123
+ healings_this_week: HealingMetric.healings_this_week,
124
+ healings_this_month: HealingMetric.healings_this_month,
125
+ average_resolution_time: HealingMetric.average_resolution_time,
126
+ evolution_methods: HealingMetric.evolution_method_distribution,
127
+ ai_providers: HealingMetric.ai_provider_distribution,
128
+ top_error_classes: HealingMetric.top_error_classes(5),
129
+ top_classes_healed: HealingMetric.top_classes_healed(5),
130
+ daily_trend: HealingMetric.daily_healing_trend(7),
131
+ hourly_distribution: HealingMetric.hourly_healing_distribution
132
+ }
133
+ end
134
+ end
135
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CodeHealer
4
- VERSION = "0.1.12"
4
+ VERSION = "0.1.15"
5
5
  end
@@ -0,0 +1,356 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Healing Details - CodeHealer Dashboard</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ color: #333;
19
+ }
20
+
21
+ .container {
22
+ max-width: 1200px;
23
+ margin: 0 auto;
24
+ padding: 20px;
25
+ }
26
+
27
+ .header {
28
+ text-align: center;
29
+ margin-bottom: 40px;
30
+ color: white;
31
+ }
32
+
33
+ .header h1 {
34
+ font-size: 2.5rem;
35
+ font-weight: 700;
36
+ margin-bottom: 10px;
37
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
38
+ }
39
+
40
+ .back-link {
41
+ color: white;
42
+ text-decoration: none;
43
+ font-size: 1.1rem;
44
+ opacity: 0.9;
45
+ }
46
+
47
+ .back-link:hover {
48
+ opacity: 1;
49
+ }
50
+
51
+ .healing-card {
52
+ background: white;
53
+ border-radius: 16px;
54
+ padding: 30px;
55
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
56
+ margin-bottom: 30px;
57
+ }
58
+
59
+ .card-title {
60
+ font-size: 1.5rem;
61
+ font-weight: 600;
62
+ margin-bottom: 20px;
63
+ color: #333;
64
+ border-bottom: 2px solid #667eea;
65
+ padding-bottom: 10px;
66
+ }
67
+
68
+ .info-grid {
69
+ display: grid;
70
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
71
+ gap: 20px;
72
+ margin-bottom: 30px;
73
+ }
74
+
75
+ .info-section {
76
+ background: #f8f9fa;
77
+ padding: 20px;
78
+ border-radius: 12px;
79
+ border-left: 4px solid #667eea;
80
+ }
81
+
82
+ .info-section h3 {
83
+ font-size: 1.1rem;
84
+ font-weight: 600;
85
+ margin-bottom: 15px;
86
+ color: #333;
87
+ }
88
+
89
+ .info-item {
90
+ display: flex;
91
+ justify-content: space-between;
92
+ margin-bottom: 10px;
93
+ padding: 8px 0;
94
+ border-bottom: 1px solid #e9ecef;
95
+ }
96
+
97
+ .info-item:last-child {
98
+ border-bottom: none;
99
+ }
100
+
101
+ .info-label {
102
+ font-weight: 500;
103
+ color: #666;
104
+ }
105
+
106
+ .info-value {
107
+ font-weight: 600;
108
+ color: #333;
109
+ }
110
+
111
+ .status-badge {
112
+ padding: 6px 12px;
113
+ border-radius: 20px;
114
+ font-size: 0.8rem;
115
+ font-weight: 500;
116
+ text-transform: uppercase;
117
+ }
118
+
119
+ .status-success {
120
+ background-color: #d4edda;
121
+ color: #155724;
122
+ }
123
+
124
+ .status-failed {
125
+ background-color: #f8d7da;
126
+ color: #721c24;
127
+ }
128
+
129
+ .timing-chart {
130
+ background: #f8f9fa;
131
+ padding: 20px;
132
+ border-radius: 12px;
133
+ margin-top: 20px;
134
+ }
135
+
136
+ .timing-bar {
137
+ display: flex;
138
+ align-items: center;
139
+ margin-bottom: 15px;
140
+ }
141
+
142
+ .timing-label {
143
+ width: 120px;
144
+ font-weight: 500;
145
+ color: #666;
146
+ }
147
+
148
+ .timing-progress {
149
+ flex: 1;
150
+ height: 20px;
151
+ background: #e9ecef;
152
+ border-radius: 10px;
153
+ overflow: hidden;
154
+ margin: 0 15px;
155
+ }
156
+
157
+ .timing-fill {
158
+ height: 100%;
159
+ background: linear-gradient(90deg, #667eea, #764ba2);
160
+ transition: width 0.3s ease;
161
+ }
162
+
163
+ .timing-value {
164
+ width: 80px;
165
+ text-align: right;
166
+ font-weight: 600;
167
+ color: #333;
168
+ }
169
+
170
+ .error-details {
171
+ background: #fff5f5;
172
+ border: 1px solid #fed7d7;
173
+ border-radius: 12px;
174
+ padding: 20px;
175
+ margin-top: 20px;
176
+ }
177
+
178
+ .error-details h3 {
179
+ color: #c53030;
180
+ margin-bottom: 15px;
181
+ }
182
+
183
+ .code-block {
184
+ background: #2d3748;
185
+ color: #e2e8f0;
186
+ padding: 15px;
187
+ border-radius: 8px;
188
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
189
+ font-size: 0.9rem;
190
+ overflow-x: auto;
191
+ margin-top: 10px;
192
+ }
193
+
194
+ @media (max-width: 768px) {
195
+ .info-grid {
196
+ grid-template-columns: 1fr;
197
+ }
198
+
199
+ .header h1 {
200
+ font-size: 2rem;
201
+ }
202
+ }
203
+ </style>
204
+ </head>
205
+ <body>
206
+ <div class="container">
207
+ <div class="header">
208
+ <h1>🏥 Healing Details</h1>
209
+ <a href="/code_healer/dashboard" class="back-link">← Back to Dashboard</a>
210
+ </div>
211
+
212
+ <% if @healing %>
213
+ <div class="healing-card">
214
+ <div class="card-title">Healing Information</div>
215
+
216
+ <div class="info-grid">
217
+ <div class="info-section">
218
+ <h3>Basic Details</h3>
219
+ <div class="info-item">
220
+ <span class="info-label">Healing ID:</span>
221
+ <span class="info-value"><%= @healing.healing_id %></span>
222
+ </div>
223
+ <div class="info-item">
224
+ <span class="info-label">Class:</span>
225
+ <span class="info-value"><%= @healing.class_name %></span>
226
+ </div>
227
+ <div class="info-item">
228
+ <span class="info-label">Method:</span>
229
+ <span class="info-value"><%= @healing.method_name %></span>
230
+ </div>
231
+ <div class="info-item">
232
+ <span class="info-label">Status:</span>
233
+ <span class="status-badge status-<%= @healing.healing_successful ? 'success' : 'failed' %>">
234
+ <%= @healing.healing_successful ? '✅ Success' : '❌ Failed' %>
235
+ </span>
236
+ </div>
237
+ </div>
238
+
239
+ <div class="info-section">
240
+ <h3>AI & Evolution</h3>
241
+ <div class="info-item">
242
+ <span class="info-label">Evolution Method:</span>
243
+ <span class="info-value"><%= @healing.evolution_method_display %></span>
244
+ </div>
245
+ <div class="info-item">
246
+ <span class="info-label">AI Provider:</span>
247
+ <span class="info-value"><%= @healing.ai_provider_display %></span>
248
+ </div>
249
+ <div class="info-item">
250
+ <span class="info-label">AI Success:</span>
251
+ <span class="info-value"><%= @healing.ai_success ? '✅ Yes' : '❌ No' %></span>
252
+ </div>
253
+ <div class="info-item">
254
+ <span class="info-label">Tests Passed:</span>
255
+ <span class="info-value"><%= @healing.tests_passed ? '✅ Yes' : '❌ No' %></span>
256
+ </div>
257
+ </div>
258
+
259
+ <div class="info-section">
260
+ <h3>Timing & Performance</h3>
261
+ <div class="info-item">
262
+ <span class="info-label">Started:</span>
263
+ <span class="info-value"><%= @healing.healing_started_at&.strftime("%Y-%m-%d %H:%M:%S") || 'N/A' %></span>
264
+ </div>
265
+ <div class="info-item">
266
+ <span class="info-label">Completed:</span>
267
+ <span class="info-value"><%= @healing.healing_completed_at&.strftime("%Y-%m-%d %H:%M:%S") || 'N/A' %></span>
268
+ </div>
269
+ <div class="info-item">
270
+ <span class="info-label">Total Duration:</span>
271
+ <span class="info-value"><%= @healing.duration_seconds ? "#{@healing.duration_seconds}s" : 'N/A' %></span>
272
+ </div>
273
+ <div class="info-item">
274
+ <span class="info-label">Syntax Valid:</span>
275
+ <span class="info-value"><%= @healing.syntax_valid ? '✅ Yes' : '❌ No' %></span>
276
+ </div>
277
+ </div>
278
+ </div>
279
+
280
+ <div class="timing-chart">
281
+ <h3>Processing Breakdown</h3>
282
+ <div class="timing-bar">
283
+ <span class="timing-label">AI Processing:</span>
284
+ <div class="timing-progress">
285
+ <div class="timing-fill" style="width: <%= @healing.ai_processing_seconds ? (@healing.ai_processing_seconds / @healing.duration_seconds.to_f * 100).round(1) : 0 %>%"></div>
286
+ </div>
287
+ <span class="timing-value"><%= @healing.ai_processing_seconds ? "#{@healing.ai_processing_seconds}s" : 'N/A' %></span>
288
+ </div>
289
+ <div class="timing-bar">
290
+ <span class="timing-label">Git Operations:</span>
291
+ <div class="timing-progress">
292
+ <div class="timing-fill" style="width: <%= @healing.git_operations_seconds ? (@healing.git_operations_seconds / @healing.duration_seconds.to_f * 100).round(1) : 0 %>%"></div>
293
+ </div>
294
+ <span class="timing-value"><%= @healing.git_operations_seconds ? "#{@healing.git_operations_seconds}s" : 'N/A' %></span>
295
+ </div>
296
+ </div>
297
+
298
+ <% if @healing.error_class.present? %>
299
+ <div class="error-details">
300
+ <h3>Error Information</h3>
301
+ <div class="info-item">
302
+ <span class="info-label">Error Class:</span>
303
+ <span class="info-value"><%= @healing.error_class %></span>
304
+ </div>
305
+ <div class="info-item">
306
+ <span class="info-label">Error Message:</span>
307
+ <span class="info-value"><%= @healing.error_message %></span>
308
+ </div>
309
+ <div class="info-item">
310
+ <span class="info-label">File Path:</span>
311
+ <span class="info-value"><%= @healing.file_path %></span>
312
+ </div>
313
+ <% if @healing.failure_reason.present? %>
314
+ <div class="info-item">
315
+ <span class="info-label">Failure Reason:</span>
316
+ <span class="info-value"><%= @healing.failure_reason %></span>
317
+ </div>
318
+ <% end %>
319
+ </div>
320
+ <% end %>
321
+
322
+ <% if @healing.healing_branch.present? %>
323
+ <div class="info-section">
324
+ <h3>Git Operations</h3>
325
+ <div class="info-item">
326
+ <span class="info-label">Healing Branch:</span>
327
+ <span class="info-value"><%= @healing.healing_branch %></span>
328
+ </div>
329
+ <% if @healing.pull_request_url.present? %>
330
+ <div class="info-item">
331
+ <span class="info-label">Pull Request:</span>
332
+ <span class="info-value">
333
+ <a href="<%= @healing.pull_request_url %>" target="_blank" style="color: #667eea; text-decoration: none;">
334
+ 🔗 View PR
335
+ </a>
336
+ </span>
337
+ </div>
338
+ <% end %>
339
+ <div class="info-item">
340
+ <span class="info-label">PR Created:</span>
341
+ <span class="info-value"><%= @healing.pr_created ? '✅ Yes' : '❌ No' %></span>
342
+ </div>
343
+ </div>
344
+ <% end %>
345
+ </div>
346
+ <% else %>
347
+ <div class="healing-card">
348
+ <div class="error-details">
349
+ <h3>Healing Not Found</h3>
350
+ <p>The requested healing operation could not be found.</p>
351
+ </div>
352
+ </div>
353
+ <% end %>
354
+ </div>
355
+ </body>
356
+ </html>